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 "hard" 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);
+ }
+
+
+}