JSEC-40 - added initial CacheManager implementations potentially to be used in 1.0

git-svn-id: https://svn.apache.org/repos/asf/incubator/jsecurity/trunk@729811 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/org/jsecurity/cache/DefaultCacheManager.java b/src/org/jsecurity/cache/DefaultCacheManager.java
new file mode 100644
index 0000000..0db9179
--- /dev/null
+++ b/src/org/jsecurity/cache/DefaultCacheManager.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.jsecurity.cache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default memory-only based {@link CacheManager CacheManager} implementation usable in production environments.
+ * <p/>
+ * This implementation does not offer any enterprise-level features such as cache coherency, optimistic locking,
+ * failover or other similar features.  It relies on memory-based {@link java.util.Map Map} caches.  For more
+ * enterprise features, consider using an {@link org.jsecurity.cache.ehcache.EhCacheManager EhCacheManager} or other
+ * similar implementation that wraps an enterprise-grade Caching solution.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class DefaultCacheManager implements CacheManager {
+
+    /**
+     * Retains all Cache objects maintained by this cache manager.
+     */
+    private final Map<String, Cache> caches = new HashMap<String, Cache>();
+
+    public Cache getCache(String name) throws CacheException {
+        if (name == null) {
+            throw new CacheException("Cache name cannot be null.");
+        }
+
+        Cache cache;
+
+        synchronized (caches) {
+            cache = caches.get(name);
+            if (cache == null) {
+                cache = new SoftHashMapCache(name);
+                caches.put(name, cache);
+            }
+        }
+
+        return cache;
+    }
+}
diff --git a/src/org/jsecurity/cache/HashtableCache.java b/src/org/jsecurity/cache/HashtableCache.java
index 70cb018..8596465 100644
--- a/src/org/jsecurity/cache/HashtableCache.java
+++ b/src/org/jsecurity/cache/HashtableCache.java
@@ -18,82 +18,30 @@
  */

 package org.jsecurity.cache;

 

-import java.util.*;

+import java.util.Hashtable;

 

 /**

  * An implementation of the JSecurity {@link Cache} interface that uses a

  * {@link Hashtable} to store cached objects.  This implementation is only suitable for

- * development/testing use.  A more robust caching solution should be used for production

- * systems such as the {@link org.jsecurity.cache.ehcache.EhCacheManager}

+ * development/testing use as it is prone to a potential memory leak if objects are not explicitly removed

+ * from the cache.

  *

  * @author Jeremy Haile

  * @author Les Hazlewood

  * @since 0.2

  */

-@SuppressWarnings("unchecked")

-public class HashtableCache implements Cache {

+public class HashtableCache extends MapCache {

 

     /**

-     * The underlying hashtable.

-     */

-    private final Map hashtable = new Hashtable();

-

-    /**

-     * The name of this cache.

-     */

-    private final String name;

-

-    /**

-     * Creates a new cache with the given name.

+     * Creates a new <code>HashtableCache</code> instance with the specified name.

+     * <p/>

+     * This constructor simply calls <code>super(name, new {@link Hashtable Hashtable}());</code>

      *

-     * @param name the name of this cache.

+     * @param name the name to assign to the cache.

+     * @since 1.0

      */

     public HashtableCache(String name) {

-        this.name = name;

+        super(name, new Hashtable());

     }

 

-    public Object get(Object key) throws CacheException {

-        return hashtable.get(key);

-    }

-

-    public void put(Object key, Object value) throws CacheException {

-        hashtable.put(key, value);

-    }

-

-    public void remove(Object key) throws CacheException {

-        hashtable.remove(key);

-    }

-

-    public void clear() throws CacheException {

-        hashtable.clear();

-    }

-

-    public int size() {

-        return hashtable.size();

-    }

-

-    public Set keys() {

-        if (!hashtable.isEmpty()) {

-            return Collections.unmodifiableSet(hashtable.keySet());

-        } else {

-            return Collections.EMPTY_SET;

-        }

-    }

-

-    public Set values() {

-        if (!hashtable.isEmpty()) {

-            Collection values = hashtable.values();

-            if (values instanceof Set) {

-                return Collections.unmodifiableSet((Set) values);

-            } else {

-                return Collections.unmodifiableSet(new LinkedHashSet(values));

-            }

-        } else {

-            return Collections.EMPTY_SET;

-        }

-    }

-

-    public String toString() {

-        return "HashtableCache [" + name + "]";

-    }

 }
\ No newline at end of file
diff --git a/src/org/jsecurity/cache/HashtableCacheManager.java b/src/org/jsecurity/cache/HashtableCacheManager.java
index 2479e5b..c237b37 100644
--- a/src/org/jsecurity/cache/HashtableCacheManager.java
+++ b/src/org/jsecurity/cache/HashtableCacheManager.java
@@ -20,6 +20,10 @@
 

 /**

  * A {@link CacheManager} that returns {@link HashtableCache} caches.

+ * <p/>

+ * This implementation is only suitable for development/testing as hashtable-based caches are prone to

+ * potential memory leaks if objects are not explicitly removed from these caches over time.  If you are configuring

+ * your own cache manager, consider using a production quality

  *

  * @author Jeremy Haile

  * @author Les Hazlewood

diff --git a/src/org/jsecurity/cache/MapCache.java b/src/org/jsecurity/cache/MapCache.java
new file mode 100644
index 0000000..1bcce67
--- /dev/null
+++ b/src/org/jsecurity/cache/MapCache.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.jsecurity.cache;
+
+import java.util.*;
+
+/**
+ * A <code>MapCache</code> is a {@link Cache Cache} implementation that uses a backing {@link Map} instance to store
+ * and retrieve cached data.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class MapCache implements Cache {
+
+    /**
+     * Backing instance.
+     */
+    private final Map map;
+
+    /**
+     * The name of this cache.
+     */
+    private final String name;
+
+    public MapCache(String name, Map backingMap) {
+        this.name = name;
+        this.map = backingMap;
+    }
+
+    public Object get(Object key) throws CacheException {
+        return map.get(key);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public void put(Object key, Object value) throws CacheException {
+        map.put(key, value);
+    }
+
+    public void remove(Object key) throws CacheException {
+        map.remove(key);
+    }
+
+    public void clear() throws CacheException {
+        map.clear();
+    }
+
+    public int size() {
+        return map.size();
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Set keys() {
+        if (!map.isEmpty()) {
+            return Collections.unmodifiableSet(map.keySet());
+        } else {
+            return Collections.EMPTY_SET;
+        }
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Set values() {
+        if (!map.isEmpty()) {
+            Collection values = map.values();
+            if (values instanceof Set) {
+                return Collections.unmodifiableSet((Set) values);
+            } else {
+                return Collections.unmodifiableSet(new LinkedHashSet(values));
+            }
+        } else {
+            return Collections.EMPTY_SET;
+        }
+    }
+
+    public String toString() {
+        return "MapCache [" + name + "]";
+    }
+}
diff --git a/src/org/jsecurity/cache/SoftHashMapCache.java b/src/org/jsecurity/cache/SoftHashMapCache.java
new file mode 100644
index 0000000..6fa2f11
--- /dev/null
+++ b/src/org/jsecurity/cache/SoftHashMapCache.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.jsecurity.cache;
+
+import org.jsecurity.util.SoftHashMap;
+
+/**
+ * A MapCache that uses a {@link SoftHashMap SoftHashMap} as its backing map.
+ * <p/>
+ * This implementation is suitable in production environments, but does not offer any enterprise features like
+ * cache coherency, replication, optimistic locking, or other features.  It is only a memory-constrained map.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class SoftHashMapCache extends MapCache {
+
+    /**
+     * Creates a new <code>SoftHashMapCache</code> instance with the specified name.
+     * <p/>
+     * This constructor simply calls <code>super(name, new {@link SoftHashMap SoftHashMap}());</code>
+     *
+     * @param name the name to assign to the cache.
+     */
+    public SoftHashMapCache(String name) {
+        super(name, new SoftHashMap());
+    }
+}
diff --git a/src/org/jsecurity/util/SoftHashMap.java b/src/org/jsecurity/util/SoftHashMap.java
new file mode 100644
index 0000000..0b67f2f
--- /dev/null
+++ b/src/org/jsecurity/util/SoftHashMap.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.jsecurity.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.*;
+
+/**
+ * A <code><em>Soft</em>HashMap</code> is a memory-constrained map that stores its <em>values</em> in
+ * {@link SoftReference SoftReference}s.  (Contrast this with the JDK's
+ * {@link WeakHashMap WeakHashMap}, which uses weak references for its <em>keys</em>, which is of little value if you
+ * want the cache to auto-resize itself based on memory constraints).
+ * <p/>
+ * Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory
+ * limitations and garbage collection.  This ensures that the cache will not cause memory leaks by holding hard
+ * references to all of its values.
+ * <p/>
+ * This class is a generics-enabled Map based on initial ideas from Hienz Kabutz's and Sydney Redelinghuys's
+ * <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version</a>, with continued
+ * modifications.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class SoftHashMap<K, V> extends AbstractMap<K, V> {
+
+    /**
+     * The default value of the HARD_SIZE attribute, equal to 100.
+     */
+    private static final int DEFAULT_HARD_SIZE = 100;
+
+    /**
+     * The internal HashMap that will hold the SoftReference.
+     */
+    private final Map<K, SoftValue<V, K>> map = new HashMap<K, SoftValue<V, K>>();
+
+    /**
+     * The number of &quot;hard&quot; references to hold internally, that is, the number of instances to prevent
+     * from being garbage collected automatically (unlike other soft references).
+     */
+    private final int HARD_SIZE;
+
+    /**
+     * The FIFO list of hard references (not to be garbage collected), order of last access.
+     */
+    private final LinkedList<V> hardCache = new LinkedList<V>();
+
+    /**
+     * Reference queue for cleared SoftReference objects.
+     */
+    private final ReferenceQueue<? super V> queue = new ReferenceQueue<V>();
+
+    public SoftHashMap() {
+        this(DEFAULT_HARD_SIZE);
+    }
+
+    public SoftHashMap(int hardSize) {
+        HARD_SIZE = hardSize;
+    }
+
+    public V get(Object key) {
+
+        V result = null;
+
+        SoftValue<V, K> value = map.get(key);
+
+        if (value != null) {
+            //unwrap the 'real' value from the SoftReference
+            result = value.get();
+            if (result == null) {
+                //The wrapped value was garbage collected, so remove this entry from the backing map:
+                map.remove(key);
+            } else {
+                //Add this value to the beginning of the 'hard' reference queue (FIFO).
+                hardCache.addFirst(result);
+
+                //trim the hard ref queue if necessary:
+                if (hardCache.size() > HARD_SIZE) {
+                    hardCache.removeLast();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * We define our own subclass of SoftReference which contains
+     * not only the value but also the key to make it easier to find
+     * the entry in the HashMap after it's been garbage collected.
+     */
+    private static class SoftValue<V, K> extends SoftReference<V> {
+
+        private final K key;
+
+        /**
+         * Constructs a new instance, wrapping the value, key, and queue, as
+         * required by the superclass.
+         *
+         * @param value the map value
+         * @param key   the map key
+         * @param queue the soft reference queue to poll to determine if the entry had been reaped by the GC.
+         */
+        private SoftValue(V value, K key, ReferenceQueue<? super V> queue) {
+            super(value, queue);
+            this.key = key;
+        }
+    }
+
+    /**
+     * Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map
+     * by looking them up using the SoftValue.key data member.
+     */
+    private void processQueue() {
+        SoftValue sv;
+        while ((sv = (SoftValue) queue.poll()) != null) {
+            map.remove(sv.key); // we can access private data!
+        }
+    }
+
+    /**
+     * Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection.
+     */
+    public V put(K key, V value) {
+        processQueue(); // throw out garbage collected values first
+        SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
+        return map.put(key, sv).get();
+    }
+
+    public V remove(Object key) {
+        processQueue(); // throw out garbage collected values first
+        SoftValue<V, K> raw = map.remove(key);
+        V removed = null;
+        if (raw != null) {
+            removed = raw.get();
+        }
+        return removed;
+    }
+
+    public void clear() {
+        hardCache.clear();
+        processQueue(); // throw out garbage collected values
+        map.clear();
+    }
+
+    public int size() {
+        processQueue(); // throw out garbage collected values first
+        return map.size();
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Set<Map.Entry<K, V>> entrySet() {
+        processQueue(); // throw out garbage collected values first
+        Set set = map.entrySet();
+        return Collections.unmodifiableSet(set);
+    }
+
+
+}