Commit 29a52862 authored by Pete Matern's avatar Pete Matern Committed by pete

Moved some cache related classes to org.jivesoftware.util.cache. Made Cache an...

Moved some cache related classes to org.jivesoftware.util.cache. Made Cache an interface and moved its implementation down into DefaultCache. Initial pass at porting CacheFactory from clearspace.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8139 b35dd754-fafc-0310-a699-88a17e54d16e
parent da320be4
......@@ -12,8 +12,8 @@
package org.jivesoftware.openfire;
import org.dom4j.Element;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
......
......@@ -17,6 +17,8 @@ import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener;
......
......@@ -11,9 +11,9 @@
package org.jivesoftware.openfire;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.Cacheable;
/**
* Represents a set of permissions that an entity has for an object in the system. For example,
......
......@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.auth;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
......
......@@ -11,7 +11,8 @@
package org.jivesoftware.openfire.filetransfer;
import org.dom4j.Element;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.DefaultCache;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.container.BasicModule;
......@@ -58,7 +59,7 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans
size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size);
expirationTime = (long) JiveGlobals.getIntProperty(
"cache." + propertiesName + ".expirationTime", (int) expirationTime);
return new Cache<String, FileTransfer>(name, size, expirationTime);
return new DefaultCache<String, FileTransfer>(name, size, expirationTime);
}
/**
......
......@@ -10,8 +10,8 @@
*/
package org.jivesoftware.openfire.filetransfer;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import java.io.Serializable;
......
......@@ -10,7 +10,7 @@
*/
package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.cache.CacheSizes;
import java.util.concurrent.Future;
import java.io.IOException;
......
......@@ -11,6 +11,8 @@
package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.DefaultCache;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.filetransfer.FileTransferManager;
import org.jivesoftware.openfire.filetransfer.FileTransferRejectedException;
......@@ -58,7 +60,7 @@ public class ProxyConnectionManager {
public ProxyConnectionManager(FileTransferManager manager) {
String cacheName = "File Transfer";
connectionMap = new Cache<String, ProxyTransfer>(cacheName, -1, 1000 * 60 * 10);
connectionMap = new DefaultCache<String, ProxyTransfer>(cacheName, -1, 1000 * 60 * 10);
className = JiveGlobals.getProperty("provider.transfer.proxy",
"org.jivesoftware.openfire.filetransfer.proxy.DefaultProxyTransfer");
......
......@@ -9,7 +9,7 @@
*/
package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.openfire.filetransfer.FileTransferProgress;
import java.io.IOException;
......
......@@ -14,8 +14,8 @@ package org.jivesoftware.openfire.group;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log;
import org.xmpp.packet.JID;
......
......@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.group;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.openfire.event.GroupEventListener;
......
......@@ -11,6 +11,8 @@
package org.jivesoftware.openfire.http;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
......
......@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.ldap;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.auth.AuthProvider;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.user.UserNotFoundException;
......
......@@ -12,8 +12,8 @@
package org.jivesoftware.openfire.privacy;
import org.dom4j.Element;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.user.UserNotFoundException;
......
......@@ -13,8 +13,8 @@ package org.jivesoftware.openfire.privacy;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.roster.Roster;
......
package org.jivesoftware.openfire.privacy;
import org.dom4j.Element;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
/**
* A Privacy list manager creates, gets, updates and removes privacy lists. Loaded lists
......
......@@ -12,8 +12,8 @@
package org.jivesoftware.openfire.roster;
import org.jivesoftware.database.JiveID;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.*;
......
......@@ -11,8 +11,8 @@
package org.jivesoftware.openfire.roster;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.IntEnum;
import org.jivesoftware.openfire.SharedGroupException;
import org.jivesoftware.openfire.group.Group;
......
......@@ -11,8 +11,8 @@
package org.jivesoftware.openfire.roster;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.ChannelHandler;
import org.jivesoftware.openfire.RoutingTable;
......
......@@ -16,6 +16,8 @@ import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.PresenceManager;
import org.jivesoftware.openfire.SessionManager;
......
......@@ -16,8 +16,8 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.ExternalizableUtil;
......
......@@ -19,6 +19,8 @@ import org.jivesoftware.openfire.event.UserEventListener;
import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
......
......@@ -13,6 +13,8 @@ package org.jivesoftware.openfire.vcard;
import org.dom4j.Element;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
......
......@@ -15,6 +15,8 @@ import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
......
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* Exception thrown during application or component initialization failure.
*/
public class InitializationException extends Exception {
private Throwable nestedThrowable = null;
public InitializationException() {
super();
}
public InitializationException(String msg) {
super(msg);
}
public InitializationException(Throwable nestedThrowable) {
this.nestedThrowable = nestedThrowable;
}
public InitializationException(String msg, Throwable nestedThrowable) {
super(msg);
this.nestedThrowable = nestedThrowable;
}
public void printStackTrace() {
super.printStackTrace();
if (nestedThrowable != null) {
nestedThrowable.printStackTrace();
}
}
public void printStackTrace(PrintStream ps) {
super.printStackTrace(ps);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(ps);
}
}
public void printStackTrace(PrintWriter pw) {
super.printStackTrace(pw);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(pw);
}
}
}
......@@ -18,6 +18,8 @@ import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import java.io.*;
import java.net.URL;
......
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.util.cache;
import org.jivesoftware.util.*;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.*;
/**
* General purpose cache. It stores objects associated with unique keys in
* memory for fast access. All keys and values added to the cache must
* implement the Serializable interface. Values may implement the Cacheable
* interface, which allows the cache to determine object size much more quickly.
* These restrictions allow a cache to never grow larger than a specified number
* of bytes and to optionally be distributed over a cluster of servers.<p>
*
* If the cache does grow too large, objects will be removed such that those
* that are accessed least frequently are removed first. Because expiration
* happens automatically, the cache makes <b>no</b> gaurantee as to how long
* an object will remain in cache after it is put in.<p>
*
* Optionally, a maximum lifetime for all objects can be specified. In that
* case, objects will be deleted from cache after that amount of time, even
* if they are frequently accessed. This feature is useful if objects put in
* cache represent data that should be periodically refreshed; for example,
* information from a database.<p>
*
* All cache operations are thread safe.<p>
*
* @see Cacheable
*/
public interface Cache<K,V> extends java.util.Map<K,V> {
/**
* Returns the name of the cache.
*
* @return the name of the cache.
*/
String getName();
/**
* Sets the name of the cache
*
* @param name the name of the cache
*/
void setName(String name);
/**
* Returns the maximum size of the cache in bytes. If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @return the maximum size of the cache in bytes.
*/
int getMaxCacheSize();
/**
* Sets the maximum size of the cache in bytes. If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @param maxSize the maximum size of the cache in bytes.
*/
void setMaxCacheSize(int maxSize);
/**
* Returns the maximum number of milliseconds that any object can live
* in cache. Once the specified number of milliseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @return the maximum number of milliseconds before objects are expired.
*/
long getMaxLifetime();
/**
* Sets the maximum number of milliseconds that any object can live
* in cache. Once the specified number of milliseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @param maxLifetime the maximum number of milliseconds before objects are expired.
*/
void setMaxLifetime(long maxLifetime);
/**
* Returns the size of the cache contents in bytes. This value is only a
* rough approximation, so cache users should expect that actual VM
* memory used by the cache could be significantly higher than the value
* reported by this method.
*
* @return the size of the cache contents in bytes.
*/
int getCacheSize();
/**
* Returns the number of cache hits. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
long getCacheHits();
/**
* Returns the number of cache misses. A cache miss occurs every
* time the get method is called and the cache does not contain the
* requested object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
long getCacheMisses();
}
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util.cache;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.InitializationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.*;
/**
* Creates Cache objects. The returned caches will either be local or clustered
* depending on the clustering enabled setting and a user's license.<p>
* <p/>
* When clustered caching is turned on, cache usage statistics for all caches
* that have been created are periodically published to the clustered cache
* named "opt-$cacheStats".
*
* @src.include false
*/
public class CacheFactory {
public static String CLUSTER_PROPERTY_NAME = "cache.clustering.enabled";
public static String LOCAL_CACHE_PROPERTY_NAME = "cache.clustering.local.class";
public static String CLUSTERED_CACHE_PROPERTY_NAME = "cache.clustering.clustered.class";
private static boolean clusteringEnabled = false;
/**
* Storage for all caches that get created.
*/
private static Map<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
/**
* List of registered listeners to be notified when clustering is enabled or disabled.
*/
private static List<ClusteringListener> listeners = new CopyOnWriteArrayList<ClusteringListener>();
private static String localCacheFactoryClass;
private static String clusteredCacheFactoryClass;
private static CacheFactoryStrategy cacheFactoryStrategy;
static {
localCacheFactoryClass = JiveGlobals.getProperty(LOCAL_CACHE_PROPERTY_NAME,
"com.jivesoftware.base.coherence.cache.CoherenceLocalCacheFactory");
clusteredCacheFactoryClass = JiveGlobals.getProperty(CLUSTERED_CACHE_PROPERTY_NAME,
"com.jivesoftware.base.coherence.cache.CoherenceClusteredCacheFactory");
}
private CacheFactory() {
}
/**
* Returns an array of all caches in the system.
* @return an array of all caches in the system.
*/
public static synchronized Cache[] getAllCaches() {
List<Cache> values = new ArrayList<Cache>();
for (Cache cache : caches.values()) {
values.add(cache);
}
return values.toArray(new Cache[values.size()]);
}
/**
* Returns the named cache, creating it as necessary.
*
* @param name the name of the cache to create.
* @return the named cache, creating it as necessary.
*/
@SuppressWarnings("unchecked")
public static synchronized <T extends Cache> T createCache(String name) {
T cache = (T) caches.get(name);
if (cache != null) {
return cache;
}
cache = (T) cacheFactoryStrategy.createCache(name);
return wrapCache(cache, name);
}
public static void lockKey(Object key, long timeout) {
cacheFactoryStrategy.lockKey(key, timeout);
}
public static void unlockKey(Object key) {
cacheFactoryStrategy.unlockKey(key);
}
@SuppressWarnings("unchecked")
private static <T extends Cache> T wrapCache(T cache, String name) {
cache = (T) new CacheWrapper(cache);
cache.setName(name);
caches.put(name, cache);
return cache;
}
/**
* Returns true if this node is currently a member of a cluster. The last step of application
* initialization is to join a cluster, so this method returns false during most of application startup.
*
* @return true if this node is currently a member of a cluster.
*/
public static boolean isClusteringEnabled() {
return clusteringEnabled;
}
/**
* Returns true if this instance is configured to run in a cluster.
* @return true if this instance is configured to run in a cluster.
*/
public static boolean isClusteringConfigured() {
return JiveGlobals.getBooleanProperty(CLUSTER_PROPERTY_NAME);
}
/**
* Returns a string uniquely identifying this member of the cluster.
*
* @return a string uniquely identifying this member of the cluster.
*/
public static String getClusterMemberID() {
return cacheFactoryStrategy.getClusterMemberID();
}
/**
* Sets whether cache clustering should be enabled. Anytime this value is
* changed, the application server must be restarted
*
* @param enabled true if cache clustering should be enabled.
* @throws Exception if an error occurs while using the new cache type.
*/
public static synchronized void setClusteringEnabled(boolean enabled) throws Exception {
if (enabled == clusteringEnabled) {
return;
}
JiveGlobals.setProperty(CLUSTER_PROPERTY_NAME, String.valueOf(enabled));
if (!enabled) {
clusteringEnabled = false;
CacheFactoryStrategy clusteredFactory = cacheFactoryStrategy;
cacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(localCacheFactoryClass)
.newInstance();
// Loop through clustered caches and change them to local caches.
for (String cacheName : caches.keySet()) {
CacheWrapper wrapper = (CacheWrapper) caches.get(cacheName);
wrapper.setWrappedCache(cacheFactoryStrategy.createCache(cacheName));
}
// Stop the cluster
clusteredFactory.stopCluster();
fireClusteringStopped();
}
else {
// Reload Jive properties. This will ensure that this nodes copy of the
// properties starts correct.
//TODO see if there is something analagous in openfire
// DbJiveProperties.getInstance().init();
startClustering();
}
}
public synchronized static void clearCaches() {
for (String cacheName : caches.keySet()) {
Cache cache = caches.get(cacheName);
cache.clear();
}
}
/**
* Returns true if this member is the senior member in the cluster. If clustering
* is not enabled, this method will also return true. This test is useful for
* tasks that should only be run on a single member in a cluster.
*
* @return true if this cluster member is the senior or if clustering is not enabled.
*/
public static boolean isSeniorClusterMember() {
synchronized(CacheFactory.class) {
if (!isClusteringEnabled()) {
return true;
}
}
return cacheFactoryStrategy.isSeniorClusterMember();
}
/**
* Invokes a task on other cluster members in an asynchronous fashion. The task will not be
* executed on the local cluster member. If clustering is not enabled, this method
* will do nothing.
*
* @param task the task to be invoked on all other cluster members.
*/
public static void doClusterTask(final ClusterTask task) {
if (!clusteringEnabled) {
return;
}
synchronized(CacheFactory.class) {
if (!clusteringEnabled) {
return;
}
}
cacheFactoryStrategy.doClusterTask(buildClusterTask(task));
}
/**
* Invokes a task on other cluster members synchronously and returns the result as a Collection
* (method will not return until the task has been executed on each cluster member).
* The task will not be executed on the local cluster member. If clustering is not enabled,
* this method will return an empty collection.
*
* @param task the ClusterTask object to be invoked on all other cluster members.
* @param includeLocalMember true to run the task on the local member, false otherwise
* @return collection with the result of the execution.
*/
public static Collection<Object> doSynchronousClusterTask(ClusterTask task, boolean includeLocalMember) {
synchronized(CacheFactory.class) {
if (!clusteringEnabled) {
return Collections.emptyList();
}
}
return cacheFactoryStrategy.doSynchronousClusterTask(buildClusterTask(task), includeLocalMember);
}
/**
* Shuts down the clustering service. This method should be called when the Jive
* system is shutting down, and must not be called otherwise. By default, a
* ServletContextListener is registered to listen for the web application shutting down, and
* will automatically call this method. However, if the Jive system is being used in
* another context, such as a command-line application, this method should be called
* explicitly. Failing to call this method may temporarily impact cluster performance,
* as the system will have to do extra work to recover from a non-clean shutdown.
* If clustering is not enabled, this method will do nothing.
*/
public static synchronized void shutdownClusteringService() {
Log.debug("Shutting down clustered cache service.");
cacheFactoryStrategy.stopCluster();
}
public static void addClusteringListener(ClusteringListener listener) {
listeners.add(listener);
}
public static void removeClusteringListener(ClusteringListener listener) {
listeners.remove(listener);
}
private static void fireClusteringStarted() {
for (ClusteringListener listener : listeners) {
(listener).clusteringStarted();
}
}
private static void fireClusteringStopped() {
for (ClusteringListener listener : listeners) {
(listener).clusteringStopped();
}
}
/**
* Saves current cache settings to local properties.
*/
public static void saveCacheSettings() {
for (Cache toSave : caches.values()) {
setMaxSizeProperty(toSave.getName(), toSave.getMaxCacheSize());
setMaxLifetimeProperty(toSave.getName(), toSave.getMaxLifetime());
}
}
/**
* Sets a local property which overrides the maximum cache size as configured in coherence-cache-config.xml for the
* supplied cache name.
* @param cacheName the name of the cache to store a value for.
* @param size the maximum cache size.
*/
public static void setMaxSizeProperty(String cacheName, int size) {
cacheName = cacheName.replaceAll(" ", "");
JiveGlobals.setProperty("cache." + cacheName + ".size", Integer.toString(size));
}
/**
* Sets a local property which overrides the maximum cache entry lifetime as configured in coherence-cache-config.xml
* for the supplied cache name.
* @param cacheName the name of the cache to store a value for.
* @param lifetime the maximum cache entry lifetime.
*/
public static void setMaxLifetimeProperty(String cacheName, long lifetime) {
cacheName = cacheName.replaceAll(" ", "");
JiveGlobals.setProperty("cache." + cacheName + ".maxLifetime", Long.toString(lifetime));
}
/**
* If a local property is found for the supplied name which specifies a value for cache size, it is returned. Otherwise,
* the defaultSize argument is returned.
* @param cacheName the name of the cache to look up a corresponding property for.
* @param defaultSize the value to return if no property is set.
* @return either the property value or the default value.
*/
public static int getMaxSizeFromProperty(String cacheName, int defaultSize) {
String propName = "cache." + cacheName.replaceAll(" ", "") + ".size";
String sizeProp = JiveGlobals.getProperty(propName);
if (sizeProp != null) {
try {
return Integer.parseInt(sizeProp);
}
catch (NumberFormatException nfe) {
Log.warn("Unable to parse " + propName + " using default value of " + defaultSize);
return defaultSize;
}
}
else {
return defaultSize;
}
}
/**
* If a local property is found for the supplied name which specifies a value for cache entry lifetime, it is returned.
* Otherwise, the defaultLifetime argument is returned.
* @param cacheName the name of the cache to look up a corresponding property for.
* @param defaultLifetime the value to return if no property is set.
* @return either the property value or the default value.
*/
public static long getMaxLifetimeFromProperty(String cacheName, long defaultLifetime) {
String propName = "cache." + cacheName.replaceAll(" ", "") + ".maxLifetime";
String lifetimeProp = JiveGlobals.getProperty(propName);
if (lifetimeProp != null) {
try {
return Long.parseLong(lifetimeProp);
}
catch (NumberFormatException nfe) {
Log.warn("Unable to parse " + propName + " using default value of " + defaultLifetime);
return defaultLifetime;
}
}
else {
return defaultLifetime;
}
}
public static synchronized void initialize() throws InitializationException {
try {
cacheFactoryStrategy = (CacheFactoryStrategy) Class
.forName(localCacheFactoryClass).newInstance();
}
catch (InstantiationException e) {
throw new InitializationException(e);
}
catch (IllegalAccessException e) {
throw new InitializationException(e);
}
catch (ClassNotFoundException e) {
throw new InitializationException(e);
}
}
/**
* Starts the cluster service if clustering is enabled, and begins tracking cache statistics. Before this method is called,
* any {@link Cache}s returned by calls to {@link #createCache} will return local caches. The process of starting clustering
* will recreate them as distributed caches. This is safer than the alternative - where clustering is started before
* any caches are created. In that scenario, cluster tasks can fire off in this process before it is safe for them to do so,
* and cluster wide deadlocks can occur.
*/
public static synchronized void startup() {
if (clusteringEnabled) {
return;
}
// See if clustering should be enabled.
String enabled = JiveGlobals.getProperty(CLUSTER_PROPERTY_NAME);
// If the user tried to turn on clustering, make sure they're actually allowed to.
if (Boolean.valueOf(enabled)) {
startClustering();
}
// Start a timing thread with 1 second of accuracy.
Thread t = new Thread("Cache Stats") {
private volatile boolean destroyed = false;
public void run() {
// Run the timer indefinitely.
//TODO set the destroyed flag through some listener
while (!destroyed) {
// Publish cache stats for this cluster node (assuming clustering is
// enabled and there are stats to publish).
try {
cacheFactoryStrategy.updateCacheStats(caches);
}
catch (Exception e) {
Log.error(e);
}
try {
// Sleep 10 seconds.
sleep(10000);
}
catch (InterruptedException ie) {
// Ignore.
}
}
Log.debug("Cache stats thread terminated.");
}
};
t.setDaemon(true);
t.start();
}
private static void startClustering() {
clusteringEnabled = false;
try {
cacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(clusteredCacheFactoryClass)
.newInstance();
boolean clusterStarted = cacheFactoryStrategy.startCluster();
if (clusterStarted) {
// Loop through local caches and switch them to clustered cache.
for (String cacheName : caches.keySet()) {
CacheWrapper wrapper = (CacheWrapper) caches.get(cacheName);
wrapper.setWrappedCache(cacheFactoryStrategy.createCache(cacheName));
}
clusteringEnabled = true;
fireClusteringStarted();
}
}
catch (Exception e) {
Log.error("Unable to start clustering - continuing in local mode", e);
}
}
/**
* Creates a new Cluster Task that will execute the wrapped task and ensure
* that any DAO context is also closed after the execution of the task.
*
* @param task the cluster task to wrap.
* @return new Cluster Task that will execute the wrapped task.
*/
public static ClusterTask buildClusterTask(final ClusterTask task) {
return new ClusterTask() {
ClusterTask taskToRun = task;
public void run() {
task.run();
}
public Object getResult() {
return task.getResult();
}
};
}
/**
* Listener interface for any object which needs to be notified when clustering starts or stops
*/
public static interface ClusteringListener {
public void clusteringStarted();
public void clusteringStopped();
}
}
\ No newline at end of file
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util.cache;
import java.util.Collection;
import java.util.Map;
/**
* Implementation of CacheFactory that relies on the specific clustering solution.
*
* @author Gaston Dombiak
*/
public interface CacheFactoryStrategy {
/**
* Returns true if the cluster has been started. When running in local
* mode a true value should be returned.<p>
*
* An error should be logged when the cluster fails to be started.
*
* @return true if the cluster has been started.
*/
boolean startCluster();
/**
* Stops the cluster. When not running in a cluster this request will be ignored.
*/
void stopCluster();
/**
* Creates a new cache for the cache name specified. The created cache is
* already configured. Different implementations could store the cache
* configuration in different ways. It is recommended to store the cache
* configuration in an external file so it is easier for customers to change
* the default configuration.
*
* @param name name of the cache to create.
* @return newly created and configured cache.
*/
Cache createCache(String name);
/**
* Returns true if this node is the maste node of the cluster. When not running
* in cluster mode a value of true should be returned.
*
* @return true if this node is the maste node of the cluster.
*/
boolean isSeniorClusterMember();
/**
* Returns a string uniquely identifying this member within the cluster.
*
* @return a string uniquely identifying this member within the cluster.
*/
String getClusterMemberID();
/**
* Invokes a task on other cluster members in an asynchronous fashion. The task will not be
* executed on the local cluster member. If clustering is not enabled, this method
* will do nothing.
*
* @param task the task to be invoked on all other cluster members.
*/
void doClusterTask(final ClusterTask task);
/**
* Invokes a task on other cluster members synchronously and returns the result as a Collection
* (method will not return until the task has been executed on each cluster member).
* The task will not be executed on the local cluster member. If clustering is not enabled,
* this method will return an empty collection.
*
* @param task the ClusterTask object to be invoked on all other cluster members.
* @param includeLocalMember true to run the task on the local member, false otherwise
* @return collection with the result of the execution.
*/
Collection<Object> doSynchronousClusterTask(ClusterTask task, boolean includeLocalMember);
/**
* Updates the statistics of the specified caches and publishes them into
* a cache for statistics. The statistics cache is already known to the application
* but this could change in the future (?). When not in cluster mode then
* do nothing.<p>
*
* The statistics cache must contain a long array of 5 positions for each cache
* with the following content:
* <ol>
* <li>cache.getCacheSize()</li>
* <li>cache.getMaxCacheSize()</li>
* <li>cache.size()</li>
* <li>cache.getCacheHits()</li>
* <li>cache.getCacheMisses()</li>
* </ol>
*
* @param caches caches to get their stats and publish them in a statistics cache.
*/
void updateCacheStats(Map<String, Cache> caches);
/**
* Locks the specified key in the locking map. The map should be clusterable
* thus locking a key is visible to the cluster. When not in cluster mode
* the lock is only visible to this JVM.
*
* @param key the key to lock.
* @param timeout number of milliseconds to wait to obtain the lock. -1 means wait forever.
*/
void lockKey(Object key, long timeout);
/**
* Unlocks the specified key in the locking map. The map should be clusterable
* thus locking a key is visible to the cluster. When not in cluster mode
* the lock is only visible to this JVM.
*
* @param key the key to unlock.
*/
void unlockKey(Object key);
}
......@@ -7,7 +7,11 @@
* Use is subject to license terms.
*/
package org.jivesoftware.util;
package org.jivesoftware.util.cache;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import java.util.Collection;
import java.util.HashMap;
......@@ -73,7 +77,7 @@ public class CacheManager {
size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size);
expirationTime = (long) JiveGlobals.getIntProperty(
"cache." + propertiesName + ".expirationTime", (int) expirationTime);
cache = new Cache<K,V>(name, size, expirationTime);
cache = new DefaultCache<K,V>(name, size, expirationTime);
caches.put(name, cache);
}
return cache;
......
......@@ -9,7 +9,9 @@
* a copy of which is included in this distribution.
*/
package org.jivesoftware.util;
package org.jivesoftware.util.cache;
import org.jivesoftware.util.cache.Cacheable;
import java.util.Map;
import java.util.Collection;
......
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util.cache;
import java.util.*;
/**
* Acts as a proxy for a Cache implementation. The Cache implementation can be switched on the fly,
* which enables users to hold a reference to a CacheWrapper object, but for the underlying
* Cache implementation to switch from clustered to local, etc.
*
*/
public class CacheWrapper<K, V> implements Cache<K, V> {
private Cache<K, V> cache;
public CacheWrapper(Cache<K, V> cache) {
this.cache = cache;
}
public Cache<K, V> getWrappedCache() {
return cache;
}
public void setWrappedCache(Cache<K, V> cache) {
this.cache = cache;
}
public String getName() {
return cache.getName();
}
public void setName(String name) {
cache.setName(name);
}
public int getMaxCacheSize() {
return cache.getMaxCacheSize();
}
public void setMaxCacheSize(int maxSize) {
cache.setMaxCacheSize(maxSize);
}
public long getMaxLifetime() {
return cache.getMaxLifetime();
}
public void setMaxLifetime(long maxLifetime) {
cache.setMaxLifetime(maxLifetime);
}
public int getCacheSize() {
return cache.getCacheSize();
}
public long getCacheHits() {
return cache.getCacheHits();
}
public long getCacheMisses() {
return cache.getCacheMisses();
}
public int size() {
return cache.size();
}
public void clear() {
cache.clear();
}
public boolean isEmpty() {
return cache.isEmpty();
}
public boolean containsKey(Object key) {
return cache.containsKey(key);
}
public boolean containsValue(Object value) {
return cache.containsValue(value);
}
public Collection<V> values() {
return cache.values();
}
public void putAll(Map<? extends K, ? extends V> t) {
cache.putAll(t);
}
public Set<Map.Entry<K, V>> entrySet() {
return cache.entrySet();
}
public Set<K> keySet() {
return cache.keySet();
}
public V get(Object key) {
return cache.get(key);
}
public V remove(Object key) {
return cache.remove(key);
}
public V put(K key, V value) {
return cache.put(key, value);
}
}
\ No newline at end of file
......@@ -9,7 +9,7 @@
* a copy of which is included in this distribution.
*/
package org.jivesoftware.util;
package org.jivesoftware.util.cache;
/**
* Interface that defines the necessary behavior for objects added to a Cache.
......@@ -22,7 +22,7 @@ package org.jivesoftware.util;
* speedy.
*
* @author Jive Software
* @see org.jivesoftware.util.Cache
* @see org.jivesoftware.util.cache.Cache
*/
public interface Cacheable extends java.io.Serializable {
......
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util.cache;
import java.io.Serializable;
/**
* An interface to mix in Serializable and Runnable, which are both required for
* sending invocable tasks across a cluster.
*/
public interface ClusterTask extends Runnable, Serializable {
public Object getResult();
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.*;
/**
* Default, non-distributed implementation of the Cache interface.
* The algorithm for cache is as follows: a HashMap is maintained for fast
* object lookup. Two linked lists are maintained: one keeps objects in the
* order they are accessed from cache, the other keeps objects in the order
* they were originally added to cache. When objects are added to cache, they
* are first wrapped by a CacheObject which maintains the following pieces
* of information:<ul>
*
* <li> The size of the object (in bytes).
* <li> A pointer to the node in the linked list that maintains accessed
* order for the object. Keeping a reference to the node lets us avoid
* linear scans of the linked list.
* <li> A pointer to the node in the linked list that maintains the age
* of the object in cache. Keeping a reference to the node lets us avoid
* linear scans of the linked list.</ul><p>
*
* To get an object from cache, a hash lookup is performed to get a reference
* to the CacheObject that wraps the real object we are looking for.
* The object is subsequently moved to the front of the accessed linked list
* and any necessary cache cleanups are performed. Cache deletion and expiration
* is performed as needed.
*
* @author Matt Tucker
*/
public class Cache<K, V> implements Map<K, V> {
/**
* The map the keys and values are stored in.
*/
protected Map<K, CacheObject<V>> map;
/**
* Linked list to maintain order that cache objects are accessed
* in, most used to least used.
*/
protected org.jivesoftware.util.LinkedList lastAccessedList;
/**
* Linked list to maintain time that cache objects were initially added
* to the cache, most recently added to oldest added.
*/
protected LinkedList ageList;
/**
* Maximum size in bytes that the cache can grow to.
*/
private int maxCacheSize;
/**
* Maintains the current size of the cache in bytes.
*/
private int cacheSize = 0;
/**
* Maximum length of time objects can exist in cache before expiring.
*/
protected long maxLifetime;
/**
* Maintain the number of cache hits and misses. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object. A cache miss represents the opposite occurence.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*/
protected long cacheHits, cacheMisses = 0L;
/**
* The name of the cache.
*/
private String name;
/**
* Create a new cache and specify the maximum size of for the cache in
* bytes, and the maximum lifetime of objects.
*
* @param name a name for the cache.
* @param maxSize the maximum size of the cache in bytes. -1 means the cache
* has no max size.
* @param maxLifetime the maximum amount of time objects can exist in
* cache before being deleted. -1 means objects never expire.
*/
public Cache(String name, int maxSize, long maxLifetime) {
this.name = name;
this.maxCacheSize = maxSize;
this.maxLifetime = maxLifetime;
// Our primary data structure is a HashMap. The default capacity of 11
// is too small in almost all cases, so we set it bigger.
map = new HashMap<K, CacheObject<V>>(103);
lastAccessedList = new LinkedList();
ageList = new LinkedList();
}
public synchronized V put(K key, V value) {
// Delete an old entry if it exists.
V answer = remove(key);
int objectSize = calculateSize(value);
// If the object is bigger than the entire cache, simply don't add it.
if (maxCacheSize > 0 && objectSize > maxCacheSize * .90) {
Log.warn("Cache: " + name + " -- object with key " + key +
" is too large to fit in cache. Size is " + objectSize);
return value;
}
cacheSize += objectSize;
CacheObject<V> cacheObject = new CacheObject<V>(value, objectSize);
map.put(key, cacheObject);
// Make an entry into the cache order list.
LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
// Store the cache order list entry so that we can get back to it
// during later lookups.
cacheObject.lastAccessedListNode = lastAccessedNode;
// Add the object to the age list
LinkedListNode ageNode = ageList.addFirst(key);
// We make an explicit call to currentTimeMillis() so that total accuracy
// of lifetime calculations is better than one second.
ageNode.timestamp = System.currentTimeMillis();
cacheObject.ageListNode = ageNode;
// If cache is too full, remove least used cache entries until it is
// not too full.
cullCache();
return answer;
}
public synchronized V get(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
CacheObject<V> cacheObject = map.get(key);
if (cacheObject == null) {
// The object didn't exist in cache, so increment cache misses.
cacheMisses++;
return null;
}
// The object exists in cache, so increment cache hits. Also, increment
// the object's read count.
cacheHits++;
cacheObject.readCount++;
// Remove the object from it's current place in the cache order list,
// and re-insert it at the front of the list.
cacheObject.lastAccessedListNode.remove();
lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
return cacheObject.object;
}
public synchronized V remove(Object key) {
CacheObject<V> cacheObject = map.get(key);
// If the object is not in cache, stop trying to remove it.
if (cacheObject == null) {
return null;
}
// remove from the hash map
map.remove(key);
// remove from the cache order list
cacheObject.lastAccessedListNode.remove();
cacheObject.ageListNode.remove();
// remove references to linked list nodes
cacheObject.ageListNode = null;
cacheObject.lastAccessedListNode = null;
// removed the object, so subtract its size from the total.
cacheSize -= cacheObject.size;
return cacheObject.object;
}
public synchronized void clear() {
Object[] keys = map.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
remove(keys[i]);
}
// Now, reset all containers.
map.clear();
lastAccessedList.clear();
lastAccessedList = new LinkedList();
ageList.clear();
ageList = new LinkedList();
cacheSize = 0;
cacheHits = 0;
cacheMisses = 0;
}
public int size() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.size();
}
public boolean isEmpty() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.isEmpty();
}
public Collection<V> values() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return new CacheObjectCollection(map.values());
}
/**
* Wraps a cached object collection to return a view of its inner objects
*/
private final class CacheObjectCollection<V> implements Collection<V> {
private Collection<CacheObject<V>> cachedObjects;
private CacheObjectCollection(Collection<CacheObject<V>> cachedObjects) {
this.cachedObjects = new ArrayList<CacheObject<V>>(cachedObjects);
}
public int size() {
return cachedObjects.size();
}
public boolean isEmpty() {
return size() == 0;
}
public boolean contains(Object o) {
Iterator<V> it = iterator();
while (it.hasNext()) {
if (it.next().equals(o)) {
return true;
}
}
return false;
}
public Iterator<V> iterator() {
return new Iterator<V>() {
private final Iterator<CacheObject<V>> it = cachedObjects.iterator();
public boolean hasNext() {
return it.hasNext();
}
public V next() {
if(it.hasNext()) {
CacheObject<V> object = it.next();
if(object == null) {
return null;
} else {
return object.object;
}
}
else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Object[] toArray() {
Object[] array = new Object[size()];
Iterator it = iterator();
int i = 0;
while (it.hasNext()) {
array[i] = it.next();
}
return array;
}
public <V>V[] toArray(V[] a) {
Iterator<V> it = (Iterator<V>) iterator();
int i = 0;
while (it.hasNext()) {
a[i++] = it.next();
}
return a;
}
public boolean containsAll(Collection<?> c) {
Iterator it = c.iterator();
while(it.hasNext()) {
if(!contains(it.next())) {
return false;
}
}
return true;
}
public boolean add(V o) {
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends V> coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
}
public boolean containsKey(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.containsKey(key);
}
public void putAll(Map<? extends K, ? extends V> map) {
for (Iterator<? extends K> i = map.keySet().iterator(); i.hasNext();) {
K key = i.next();
V value = map.get(key);
put(key, value);
}
}
public boolean containsValue(Object value) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
if(value == null) {
return containsNullValue();
}
Iterator it = values().iterator();
while(it.hasNext()) {
if(value.equals(it.next())) {
return true;
}
}
return false;
}
private boolean containsNullValue() {
Iterator it = values().iterator();
while(it.hasNext()) {
if(it.next() == null) {
return true;
}
}
return false;
}
public Set entrySet() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
// TODO Make this work right
return Collections.unmodifiableSet(map.entrySet());
}
public Set<K> keySet() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return Collections.unmodifiableSet(map.keySet());
}
/**
* Returns the name of this cache. The name is completely arbitrary
* and used only for display to administrators.
*
* @return the name of this cache.
*/
public String getName() {
return name;
}
/**
* Returns the number of cache hits. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
public long getCacheHits() {
return cacheHits;
}
/**
* Returns the number of cache misses. A cache miss occurs every
* time the get method is called and the cache does not contain the
* requested object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
public long getCacheMisses() {
return cacheMisses;
}
/**
* Returns the size of the cache contents in bytes. This value is only a
* rough approximation, so cache users should expect that actual VM
* memory used by the cache could be significantly higher than the value
* reported by this method.
*
* @return the size of the cache contents in bytes.
*/
public int getCacheSize() {
return cacheSize;
}
/**
* Returns the maximum size of the cache (in bytes). If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @return the maximum size of the cache (-1 indicates unlimited max size).
*/
public int getMaxCacheSize() {
return maxCacheSize;
}
/**
* Sets the maximum size of the cache. If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @param maxCacheSize the maximum size of this cache (-1 indicates unlimited max size).
*/
public void setMaxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
// It's possible that the new max size is smaller than our current cache
// size. If so, we need to delete infrequently used items.
cullCache();
}
/**
* Returns the maximum number of milleseconds that any object can live
* in cache. Once the specified number of milleseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @return the maximum number of milleseconds before objects are expired.
*/
public long getMaxLifetime() {
return maxLifetime;
}
/**
* Sets the maximum number of milleseconds that any object can live
* in cache. Once the specified number of milleseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @param maxLifetime the maximum number of milleseconds before objects are expired.
*/
public void setMaxLifetime(long maxLifetime) {
this.maxLifetime = maxLifetime;
}
/**
* Returns the size of an object in bytes. Determining size by serialization
* is only used as a last resort.
*
* @return the size of an object in bytes.
*/
private int calculateSize(Object object) {
// If the object is Cacheable, ask it its size.
if (object instanceof Cacheable) {
return ((Cacheable)object).getCachedSize();
}
// Check for other common types of objects put into cache.
else if (object instanceof String) {
return CacheSizes.sizeOfString((String)object);
}
else if (object instanceof Long) {
return CacheSizes.sizeOfLong();
}
else if (object instanceof Integer) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
}
else if (object instanceof Boolean) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean();
}
else if (object instanceof long[]) {
long[] array = (long[])object;
return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong();
}
else if (object instanceof byte[]) {
byte [] array = (byte[])object;
return CacheSizes.sizeOfObject() + array.length;
}
// Default behavior -- serialize the object to determine its size.
else {
int size = 1;
try {
// Default to serializing the object out to determine size.
NullOutputStream out = new NullOutputStream();
ObjectOutputStream outObj = new ObjectOutputStream(out);
outObj.writeObject(object);
size = out.size();
}
catch (IOException ioe) {
Log.error(ioe);
}
return size;
}
}
/**
* Clears all entries out of cache where the entries are older than the
* maximum defined age.
*/
protected void deleteExpiredEntries() {
// Check if expiration is turned on.
if (maxLifetime <= 0) {
return;
}
// Remove all old entries. To do this, we remove objects from the end
// of the linked list until they are no longer too old. We get to avoid
// any hash lookups or looking at any more objects than is strictly
// neccessary.
LinkedListNode node = ageList.getLast();
// If there are no entries in the age list, return.
if (node == null) {
return;
}
// Determine the expireTime, which is the moment in time that elements
// should expire from cache. Then, we can do an easy to check to see
// if the expire time is greater than the expire time.
long expireTime = System.currentTimeMillis() - maxLifetime;
while (expireTime > node.timestamp) {
// Remove the object
remove(node.object);
// Get the next node.
node = ageList.getLast();
// If there are no more entries in the age list, return.
if (node == null) {
return;
}
}
}
/**
* Removes objects from cache if the cache is too full. "Too full" is
* defined as within 3% of the maximum cache size. Whenever the cache is
* is too big, the least frequently used elements are deleted until the
* cache is at least 10% empty.
*/
protected final void cullCache() {
// Check if a max cache size is defined.
if (maxCacheSize < 0) {
return;
}
// See if the cache size is within 3% of being too big. If so, clean out
// cache until it's 10% free.
if (cacheSize >= maxCacheSize * .97) {
// First, delete any old entries to see how much memory that frees.
deleteExpiredEntries();
int desiredSize = (int)(maxCacheSize * .90);
while (cacheSize > desiredSize) {
// Get the key and invoke the remove method on it.
remove(lastAccessedList.getLast().object);
}
}
}
/**
* Wrapper for all objects put into cache. It's primary purpose is to maintain
* references to the linked lists that maintain the creation time of the object
* and the ordering of the most used objects.
*/
private static class CacheObject<V> {
/**
* Underlying object wrapped by the CacheObject.
*/
public V object;
/**
* The size of the Cacheable object. The size of the Cacheable
* object is only computed once when it is added to the cache. This makes
* the assumption that once objects are added to cache, they are mostly
* read-only and that their size does not change significantly over time.
*/
public int size;
/**
* A reference to the node in the cache order list. We keep the reference
* here to avoid linear scans of the list. Every time the object is
* accessed, the node is removed from its current spot in the list and
* moved to the front.
*/
public LinkedListNode lastAccessedListNode;
/**
* A reference to the node in the age order list. We keep the reference
* here to avoid linear scans of the list. The reference is used if the
* object has to be deleted from the list.
*/
public LinkedListNode ageListNode;
/**
* A count of the number of times the object has been read from cache.
*/
public int readCount = 0;
/**
* Creates a new cache object wrapper. The size of the Cacheable object
* must be passed in in order to prevent another possibly expensive
* lookup by querying the object itself for its size.<p>
*
* @param object the underlying Object to wrap.
* @param size the size of the Cachable object in bytes.
*/
public CacheObject(V object, int size) {
this.object = object;
this.size = size;
}
}
/**
* An extension of OutputStream that does nothing but calculate the number
* of bytes written through it.
*/
private static class NullOutputStream extends OutputStream {
int size = 0;
public void write(int b) throws IOException {
size++;
}
public void write(byte[] b) throws IOException {
size += b.length;
}
public void write(byte[] b, int off, int len) {
size += len;
}
/**
* Returns the number of bytes written out through the stream.
*
* @return the number of bytes written to the stream.
*/
public int size() {
return size;
}
}
}
\ No newline at end of file
/**
* $Revision$
* $Date$
*
* Copyright (C) 1999-2005 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.util.cache;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.LinkedListNode;
import java.util.*;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Default, non-distributed implementation of the Cache interface.
* The algorithm for cache is as follows: a HashMap is maintained for fast
* object lookup. Two linked lists are maintained: one keeps objects in the
* order they are accessed from cache, the other keeps objects in the order
* they were originally added to cache. When objects are added to cache, they
* are first wrapped by a CacheObject which maintains the following pieces
* of information:<ul>
*
* <li> The size of the object (in bytes).
* <li> A pointer to the node in the linked list that maintains accessed
* order for the object. Keeping a reference to the node lets us avoid
* linear scans of the linked list.
* <li> A pointer to the node in the linked list that maintains the age
* of the object in cache. Keeping a reference to the node lets us avoid
* linear scans of the linked list.</ul><p>
*
* To get an object from cache, a hash lookup is performed to get a reference
* to the CacheObject that wraps the real object we are looking for.
* The object is subsequently moved to the front of the accessed linked list
* and any necessary cache cleanups are performed. Cache deletion and expiration
* is performed as needed.
*
* @author Matt Tucker
*/
public class DefaultCache<K, V> implements Cache<K, V> {
/**
* The map the keys and values are stored in.
*/
protected Map<K, DefaultCache.CacheObject<V>> map;
/**
* Linked list to maintain order that cache objects are accessed
* in, most used to least used.
*/
protected org.jivesoftware.util.LinkedList lastAccessedList;
/**
* Linked list to maintain time that cache objects were initially added
* to the cache, most recently added to oldest added.
*/
protected org.jivesoftware.util.LinkedList ageList;
/**
* Maximum size in bytes that the cache can grow to.
*/
private int maxCacheSize;
/**
* Maintains the current size of the cache in bytes.
*/
private int cacheSize = 0;
/**
* Maximum length of time objects can exist in cache before expiring.
*/
protected long maxLifetime;
/**
* Maintain the number of cache hits and misses. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object. A cache miss represents the opposite occurence.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*/
protected long cacheHits, cacheMisses = 0L;
/**
* The name of the cache.
*/
private String name;
/**
* Create a new default cache and specify the maximum size of for the cache in
* bytes, and the maximum lifetime of objects.
*
* @param name a name for the cache.
* @param maxSize the maximum size of the cache in bytes. -1 means the cache
* has no max size.
* @param maxLifetime the maximum amount of time objects can exist in
* cache before being deleted. -1 means objects never expire.
*/
public DefaultCache(String name, int maxSize, long maxLifetime) {
this.name = name;
this.maxCacheSize = maxSize;
this.maxLifetime = maxLifetime;
// Our primary data structure is a HashMap. The default capacity of 11
// is too small in almost all cases, so we set it bigger.
map = new HashMap<K, CacheObject<V>>(103);
lastAccessedList = new org.jivesoftware.util.LinkedList();
ageList = new org.jivesoftware.util.LinkedList();
}
public synchronized V put(K key, V value) {
// Delete an old entry if it exists.
V answer = remove(key);
int objectSize = calculateSize(value);
// If the object is bigger than the entire cache, simply don't add it.
if (maxCacheSize > 0 && objectSize > maxCacheSize * .90) {
Log.warn("Cache: " + name + " -- object with key " + key +
" is too large to fit in cache. Size is " + objectSize);
return value;
}
cacheSize += objectSize;
DefaultCache.CacheObject<V> cacheObject = new DefaultCache.CacheObject<V>(value, objectSize);
map.put(key, cacheObject);
// Make an entry into the cache order list.
LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
// Store the cache order list entry so that we can get back to it
// during later lookups.
cacheObject.lastAccessedListNode = lastAccessedNode;
// Add the object to the age list
LinkedListNode ageNode = ageList.addFirst(key);
// We make an explicit call to currentTimeMillis() so that total accuracy
// of lifetime calculations is better than one second.
ageNode.timestamp = System.currentTimeMillis();
cacheObject.ageListNode = ageNode;
// If cache is too full, remove least used cache entries until it is
// not too full.
cullCache();
return answer;
}
public synchronized V get(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
DefaultCache.CacheObject<V> cacheObject = map.get(key);
if (cacheObject == null) {
// The object didn't exist in cache, so increment cache misses.
cacheMisses++;
return null;
}
// The object exists in cache, so increment cache hits. Also, increment
// the object's read count.
cacheHits++;
cacheObject.readCount++;
// Remove the object from it's current place in the cache order list,
// and re-insert it at the front of the list.
cacheObject.lastAccessedListNode.remove();
lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
return cacheObject.object;
}
public synchronized V remove(Object key) {
DefaultCache.CacheObject<V> cacheObject = map.get(key);
// If the object is not in cache, stop trying to remove it.
if (cacheObject == null) {
return null;
}
// remove from the hash map
map.remove(key);
// remove from the cache order list
cacheObject.lastAccessedListNode.remove();
cacheObject.ageListNode.remove();
// remove references to linked list nodes
cacheObject.ageListNode = null;
cacheObject.lastAccessedListNode = null;
// removed the object, so subtract its size from the total.
cacheSize -= cacheObject.size;
return cacheObject.object;
}
public synchronized void clear() {
Object[] keys = map.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
remove(keys[i]);
}
// Now, reset all containers.
map.clear();
lastAccessedList.clear();
lastAccessedList = new org.jivesoftware.util.LinkedList();
ageList.clear();
ageList = new org.jivesoftware.util.LinkedList();
cacheSize = 0;
cacheHits = 0;
cacheMisses = 0;
}
public int size() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.size();
}
public boolean isEmpty() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.isEmpty();
}
public Collection<V> values() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return new DefaultCache.CacheObjectCollection(map.values());
}
/**
* Wraps a cached object collection to return a view of its inner objects
*/
private final class CacheObjectCollection<V> implements Collection<V> {
private Collection<DefaultCache.CacheObject<V>> cachedObjects;
private CacheObjectCollection(Collection<DefaultCache.CacheObject<V>> cachedObjects) {
this.cachedObjects = new ArrayList<CacheObject<V>>(cachedObjects);
}
public int size() {
return cachedObjects.size();
}
public boolean isEmpty() {
return size() == 0;
}
public boolean contains(Object o) {
Iterator<V> it = iterator();
while (it.hasNext()) {
if (it.next().equals(o)) {
return true;
}
}
return false;
}
public Iterator<V> iterator() {
return new Iterator<V>() {
private final Iterator<DefaultCache.CacheObject<V>> it = cachedObjects.iterator();
public boolean hasNext() {
return it.hasNext();
}
public V next() {
if(it.hasNext()) {
DefaultCache.CacheObject<V> object = it.next();
if(object == null) {
return null;
} else {
return object.object;
}
}
else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Object[] toArray() {
Object[] array = new Object[size()];
Iterator it = iterator();
int i = 0;
while (it.hasNext()) {
array[i] = it.next();
}
return array;
}
public <V>V[] toArray(V[] a) {
Iterator<V> it = (Iterator<V>) iterator();
int i = 0;
while (it.hasNext()) {
a[i++] = it.next();
}
return a;
}
public boolean containsAll(Collection<?> c) {
Iterator it = c.iterator();
while(it.hasNext()) {
if(!contains(it.next())) {
return false;
}
}
return true;
}
public boolean add(V o) {
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends V> coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
}
public boolean containsKey(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.containsKey(key);
}
public void putAll(Map<? extends K, ? extends V> map) {
for (Iterator<? extends K> i = map.keySet().iterator(); i.hasNext();) {
K key = i.next();
V value = map.get(key);
put(key, value);
}
}
public boolean containsValue(Object value) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
if(value == null) {
return containsNullValue();
}
Iterator it = values().iterator();
while(it.hasNext()) {
if(value.equals(it.next())) {
return true;
}
}
return false;
}
private boolean containsNullValue() {
Iterator it = values().iterator();
while(it.hasNext()) {
if(it.next() == null) {
return true;
}
}
return false;
}
public Set entrySet() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
// TODO Make this work right
return Collections.unmodifiableSet(map.entrySet());
}
public Set<K> keySet() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return Collections.unmodifiableSet(map.keySet());
}
/**
* Returns the name of this cache. The name is completely arbitrary
* and used only for display to administrators.
*
* @return the name of this cache.
*/
public String getName() {
return name;
}
/**
* Sets the name of this cache.
*
* @param name the name of this cache.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the number of cache hits. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
public long getCacheHits() {
return cacheHits;
}
/**
* Returns the number of cache misses. A cache miss occurs every
* time the get method is called and the cache does not contain the
* requested object.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*
* @return the number of cache hits.
*/
public long getCacheMisses() {
return cacheMisses;
}
/**
* Returns the size of the cache contents in bytes. This value is only a
* rough approximation, so cache users should expect that actual VM
* memory used by the cache could be significantly higher than the value
* reported by this method.
*
* @return the size of the cache contents in bytes.
*/
public int getCacheSize() {
return cacheSize;
}
/**
* Returns the maximum size of the cache (in bytes). If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @return the maximum size of the cache (-1 indicates unlimited max size).
*/
public int getMaxCacheSize() {
return maxCacheSize;
}
/**
* Sets the maximum size of the cache. If the cache grows larger
* than the max size, the least frequently used items will be removed. If
* the max cache size is set to -1, there is no size limit.
*
* @param maxCacheSize the maximum size of this cache (-1 indicates unlimited max size).
*/
public void setMaxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
// It's possible that the new max size is smaller than our current cache
// size. If so, we need to delete infrequently used items.
cullCache();
}
/**
* Returns the maximum number of milleseconds that any object can live
* in cache. Once the specified number of milleseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @return the maximum number of milleseconds before objects are expired.
*/
public long getMaxLifetime() {
return maxLifetime;
}
/**
* Sets the maximum number of milleseconds that any object can live
* in cache. Once the specified number of milleseconds passes, the object
* will be automatically expried from cache. If the max lifetime is set
* to -1, then objects never expire.
*
* @param maxLifetime the maximum number of milleseconds before objects are expired.
*/
public void setMaxLifetime(long maxLifetime) {
this.maxLifetime = maxLifetime;
}
/**
* Returns the size of an object in bytes. Determining size by serialization
* is only used as a last resort.
*
* @return the size of an object in bytes.
*/
private int calculateSize(Object object) {
// If the object is Cacheable, ask it its size.
if (object instanceof Cacheable) {
return ((Cacheable)object).getCachedSize();
}
// Check for other common types of objects put into cache.
else if (object instanceof String) {
return CacheSizes.sizeOfString((String)object);
}
else if (object instanceof Long) {
return CacheSizes.sizeOfLong();
}
else if (object instanceof Integer) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
}
else if (object instanceof Boolean) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean();
}
else if (object instanceof long[]) {
long[] array = (long[])object;
return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong();
}
else if (object instanceof byte[]) {
byte [] array = (byte[])object;
return CacheSizes.sizeOfObject() + array.length;
}
// Default behavior -- serialize the object to determine its size.
else {
int size = 1;
try {
// Default to serializing the object out to determine size.
DefaultCache.NullOutputStream out = new DefaultCache.NullOutputStream();
ObjectOutputStream outObj = new ObjectOutputStream(out);
outObj.writeObject(object);
size = out.size();
}
catch (IOException ioe) {
Log.error(ioe);
}
return size;
}
}
/**
* Clears all entries out of cache where the entries are older than the
* maximum defined age.
*/
protected void deleteExpiredEntries() {
// Check if expiration is turned on.
if (maxLifetime <= 0) {
return;
}
// Remove all old entries. To do this, we remove objects from the end
// of the linked list until they are no longer too old. We get to avoid
// any hash lookups or looking at any more objects than is strictly
// neccessary.
LinkedListNode node = ageList.getLast();
// If there are no entries in the age list, return.
if (node == null) {
return;
}
// Determine the expireTime, which is the moment in time that elements
// should expire from cache. Then, we can do an easy to check to see
// if the expire time is greater than the expire time.
long expireTime = System.currentTimeMillis() - maxLifetime;
while (expireTime > node.timestamp) {
// Remove the object
remove(node.object);
// Get the next node.
node = ageList.getLast();
// If there are no more entries in the age list, return.
if (node == null) {
return;
}
}
}
/**
* Removes objects from cache if the cache is too full. "Too full" is
* defined as within 3% of the maximum cache size. Whenever the cache is
* is too big, the least frequently used elements are deleted until the
* cache is at least 10% empty.
*/
protected final void cullCache() {
// Check if a max cache size is defined.
if (maxCacheSize < 0) {
return;
}
// See if the cache size is within 3% of being too big. If so, clean out
// cache until it's 10% free.
if (cacheSize >= maxCacheSize * .97) {
// First, delete any old entries to see how much memory that frees.
deleteExpiredEntries();
int desiredSize = (int)(maxCacheSize * .90);
while (cacheSize > desiredSize) {
// Get the key and invoke the remove method on it.
remove(lastAccessedList.getLast().object);
}
}
}
/**
* Wrapper for all objects put into cache. It's primary purpose is to maintain
* references to the linked lists that maintain the creation time of the object
* and the ordering of the most used objects.
*/
private static class CacheObject<V> {
/**
* Underlying object wrapped by the CacheObject.
*/
public V object;
/**
* The size of the Cacheable object. The size of the Cacheable
* object is only computed once when it is added to the cache. This makes
* the assumption that once objects are added to cache, they are mostly
* read-only and that their size does not change significantly over time.
*/
public int size;
/**
* A reference to the node in the cache order list. We keep the reference
* here to avoid linear scans of the list. Every time the object is
* accessed, the node is removed from its current spot in the list and
* moved to the front.
*/
public LinkedListNode lastAccessedListNode;
/**
* A reference to the node in the age order list. We keep the reference
* here to avoid linear scans of the list. The reference is used if the
* object has to be deleted from the list.
*/
public LinkedListNode ageListNode;
/**
* A count of the number of times the object has been read from cache.
*/
public int readCount = 0;
/**
* Creates a new cache object wrapper. The size of the Cacheable object
* must be passed in in order to prevent another possibly expensive
* lookup by querying the object itself for its size.<p>
*
* @param object the underlying Object to wrap.
* @param size the size of the Cachable object in bytes.
*/
public CacheObject(V object, int size) {
this.object = object;
this.size = size;
}
}
/**
* An extension of OutputStream that does nothing but calculate the number
* of bytes written through it.
*/
private static class NullOutputStream extends OutputStream {
int size = 0;
public void write(int b) throws IOException {
size++;
}
public void write(byte[] b) throws IOException {
size += b.length;
}
public void write(byte[] b, int off, int len) {
size += len;
}
/**
* Returns the number of bytes written out through the stream.
*
* @return the number of bytes written to the stream.
*/
public int size() {
return size;
}
}
}
<%@ page import="org.jivesoftware.util.Cache"%>
<%@ page import="org.jivesoftware.util.cache.Cache"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ page import="java.text.DecimalFormat"%>
<%--
......@@ -90,17 +90,17 @@
</head>
<body>
<% // Get parameters
<% // Get parameters
boolean doClearCache = request.getParameter("clear") != null;
int refresh = ParamUtils.getIntParameter(request,"refresh",-1);
int[] cacheIDs = ParamUtils.getIntParameters(request,"cacheID",-1);
int refresh = ParamUtils.getIntParameter(request, "refresh", -1);
int[] cacheIDs = ParamUtils.getIntParameters(request, "cacheID", -1);
// Get the list of existing caches
Cache[] caches = webManager.getCaches();
// Clear one or multiple caches if requested.
if (doClearCache) {
for (int i=0; i<cacheIDs.length; i++) {
for (int i = 0; i < cacheIDs.length; i++) {
caches[cacheIDs[i]].clear();
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment