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 @@ ...@@ -12,8 +12,8 @@
package org.jivesoftware.openfire; package org.jivesoftware.openfire;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.Cache; import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.CacheManager; import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.util.JiveConstants; import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider; import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
......
...@@ -17,6 +17,8 @@ import org.dom4j.io.SAXReader; ...@@ -17,6 +17,8 @@ import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager; import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.util.*; 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.container.BasicModule;
import org.jivesoftware.openfire.event.UserEventDispatcher; import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener; import org.jivesoftware.openfire.event.UserEventListener;
......
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
package org.jivesoftware.openfire; package org.jivesoftware.openfire;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.StringUtils; 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, * Represents a set of permissions that an entity has for an object in the system. For example,
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.auth; package org.jivesoftware.openfire.auth;
import org.jivesoftware.util.*; 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.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
package org.jivesoftware.openfire.filetransfer; package org.jivesoftware.openfire.filetransfer;
import org.dom4j.Element; 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.util.JiveGlobals;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
...@@ -58,7 +59,7 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans ...@@ -58,7 +59,7 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans
size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size); size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size);
expirationTime = (long) JiveGlobals.getIntProperty( expirationTime = (long) JiveGlobals.getIntProperty(
"cache." + propertiesName + ".expirationTime", (int) expirationTime); "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 @@ ...@@ -10,8 +10,8 @@
*/ */
package org.jivesoftware.openfire.filetransfer; package org.jivesoftware.openfire.filetransfer;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import java.io.Serializable; import java.io.Serializable;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
*/ */
package org.jivesoftware.openfire.filetransfer.proxy; package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.io.IOException; import java.io.IOException;
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
package org.jivesoftware.openfire.filetransfer.proxy; package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.*; 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.auth.UnauthorizedException;
import org.jivesoftware.openfire.filetransfer.FileTransferManager; import org.jivesoftware.openfire.filetransfer.FileTransferManager;
import org.jivesoftware.openfire.filetransfer.FileTransferRejectedException; import org.jivesoftware.openfire.filetransfer.FileTransferRejectedException;
...@@ -58,7 +60,7 @@ public class ProxyConnectionManager { ...@@ -58,7 +60,7 @@ public class ProxyConnectionManager {
public ProxyConnectionManager(FileTransferManager manager) { public ProxyConnectionManager(FileTransferManager manager) {
String cacheName = "File Transfer"; 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", className = JiveGlobals.getProperty("provider.transfer.proxy",
"org.jivesoftware.openfire.filetransfer.proxy.DefaultProxyTransfer"); "org.jivesoftware.openfire.filetransfer.proxy.DefaultProxyTransfer");
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
*/ */
package org.jivesoftware.openfire.filetransfer.proxy; package org.jivesoftware.openfire.filetransfer.proxy;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.openfire.filetransfer.FileTransferProgress; import org.jivesoftware.openfire.filetransfer.FileTransferProgress;
import java.io.IOException; import java.io.IOException;
......
...@@ -14,8 +14,8 @@ package org.jivesoftware.openfire.group; ...@@ -14,8 +14,8 @@ package org.jivesoftware.openfire.group;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.GroupEventDispatcher; import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.group; package org.jivesoftware.openfire.group;
import org.jivesoftware.util.*; 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.XMPPServer;
import org.jivesoftware.openfire.event.GroupEventDispatcher; import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.openfire.event.GroupEventListener; import org.jivesoftware.openfire.event.GroupEventListener;
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
package org.jivesoftware.openfire.http; package org.jivesoftware.openfire.http;
import org.jivesoftware.util.*; 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.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
package org.jivesoftware.openfire.ldap; package org.jivesoftware.openfire.ldap;
import org.jivesoftware.util.*; 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.AuthProvider;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
package org.jivesoftware.openfire.privacy; package org.jivesoftware.openfire.privacy;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem; import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
......
...@@ -13,8 +13,8 @@ package org.jivesoftware.openfire.privacy; ...@@ -13,8 +13,8 @@ package org.jivesoftware.openfire.privacy;
import org.dom4j.DocumentFactory; import org.dom4j.DocumentFactory;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.Roster;
......
package org.jivesoftware.openfire.privacy; package org.jivesoftware.openfire.privacy;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.Cache; import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.CacheManager; import org.jivesoftware.util.cache.CacheManager;
/** /**
* A Privacy list manager creates, gets, updates and removes privacy lists. Loaded lists * A Privacy list manager creates, gets, updates and removes privacy lists. Loaded lists
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
package org.jivesoftware.openfire.roster; package org.jivesoftware.openfire.roster;
import org.jivesoftware.database.JiveID; import org.jivesoftware.database.JiveID;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.JiveConstants; import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.*; import org.jivesoftware.openfire.*;
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
package org.jivesoftware.openfire.roster; package org.jivesoftware.openfire.roster;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.IntEnum; import org.jivesoftware.util.IntEnum;
import org.jivesoftware.openfire.SharedGroupException; import org.jivesoftware.openfire.SharedGroupException;
import org.jivesoftware.openfire.group.Group; import org.jivesoftware.openfire.group.Group;
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
package org.jivesoftware.openfire.roster; package org.jivesoftware.openfire.roster;
import org.jivesoftware.util.Cache; import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.CacheManager; import org.jivesoftware.util.cache.CacheManager;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.ChannelHandler; import org.jivesoftware.openfire.ChannelHandler;
import org.jivesoftware.openfire.RoutingTable; import org.jivesoftware.openfire.RoutingTable;
......
...@@ -16,6 +16,8 @@ import org.dom4j.DocumentException; ...@@ -16,6 +16,8 @@ import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.*; 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.PacketDeliverer;
import org.jivesoftware.openfire.PresenceManager; import org.jivesoftware.openfire.PresenceManager;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.SessionManager;
......
...@@ -16,8 +16,8 @@ import org.jivesoftware.openfire.XMPPServer; ...@@ -16,8 +16,8 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory; import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.event.UserEventDispatcher; import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.ExternalizableUtil; import org.jivesoftware.util.cache.ExternalizableUtil;
......
...@@ -19,6 +19,8 @@ import org.jivesoftware.openfire.event.UserEventListener; ...@@ -19,6 +19,8 @@ import org.jivesoftware.openfire.event.UserEventListener;
import org.jivesoftware.stringprep.Stringprep; import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException; import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.*; 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.IQ;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
......
...@@ -13,6 +13,8 @@ package org.jivesoftware.openfire.vcard; ...@@ -13,6 +13,8 @@ package org.jivesoftware.openfire.vcard;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.*; 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.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider; import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
......
...@@ -15,6 +15,8 @@ import org.apache.commons.httpclient.HttpClient; ...@@ -15,6 +15,8 @@ import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams; 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.ServletConfig;
import javax.servlet.ServletException; 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; ...@@ -18,6 +18,8 @@ import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.roster.RosterManager; import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheManager;
import java.io.*; import java.io.*;
import java.net.URL; 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 @@ ...@@ -7,7 +7,11 @@
* Use is subject to license terms. * 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.Collection;
import java.util.HashMap; import java.util.HashMap;
...@@ -73,7 +77,7 @@ public class CacheManager { ...@@ -73,7 +77,7 @@ public class CacheManager {
size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size); size = JiveGlobals.getIntProperty("cache." + propertiesName + ".size", size);
expirationTime = (long) JiveGlobals.getIntProperty( expirationTime = (long) JiveGlobals.getIntProperty(
"cache." + propertiesName + ".expirationTime", (int) expirationTime); "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); caches.put(name, cache);
} }
return cache; return cache;
......
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
* a copy of which is included in this distribution. * 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.Map;
import java.util.Collection; 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 @@ ...@@ -9,7 +9,7 @@
* a copy of which is included in this distribution. * 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. * Interface that defines the necessary behavior for objects added to a Cache.
...@@ -22,7 +22,7 @@ package org.jivesoftware.util; ...@@ -22,7 +22,7 @@ package org.jivesoftware.util;
* speedy. * speedy.
* *
* @author Jive Software * @author Jive Software
* @see org.jivesoftware.util.Cache * @see org.jivesoftware.util.cache.Cache
*/ */
public interface Cacheable extends java.io.Serializable { 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$
* $Revision$ * $Date$
* $Date$ *
* * Copyright (C) 1999-2005 Jive Software. All rights reserved.
* Copyright (C) 2004 Jive Software. All rights reserved. * This software is the proprietary information of Jive Software. Use is subject to license terms.
* */
* This software is published under the terms of the GNU Public License (GPL), package org.jivesoftware.util.cache;
* a copy of which is included in this distribution.
*/ import org.jivesoftware.util.Log;
import org.jivesoftware.util.LinkedListNode;
package org.jivesoftware.util;
import java.util.*;
import java.io.IOException; import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.*;
/**
/** * Default, non-distributed implementation of the Cache interface.
* Default, non-distributed implementation of the Cache interface. * The algorithm for cache is as follows: a HashMap is maintained for fast
* 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
* 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
* 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
* they were originally added to cache. When objects are added to cache, they * are first wrapped by a CacheObject which maintains the following pieces
* are first wrapped by a CacheObject which maintains the following pieces * of information:<ul>
* of information:<ul> *
* * <li> The size of the object (in bytes).
* <li> The size of the object (in bytes). * <li> A pointer to the node in the linked list that maintains accessed
* <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
* order for the object. Keeping a reference to the node lets us avoid * linear scans of the linked list.
* linear scans of the linked list. * <li> A pointer to the node in the linked list that maintains the age
* <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
* of the object in cache. Keeping a reference to the node lets us avoid * linear scans of the linked list.</ul><p>
* linear scans of the linked list.</ul><p> *
* * To get an object from cache, a hash lookup is performed to get a reference
* 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.
* 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
* The object is subsequently moved to the front of the accessed linked list * and any necessary cache cleanups are performed. Cache deletion and expiration
* and any necessary cache cleanups are performed. Cache deletion and expiration * is performed as needed.
* is performed as needed. *
* * @author Matt Tucker
* @author Matt Tucker */
*/ public class DefaultCache<K, V> implements Cache<K, V> {
public class Cache<K, V> implements Map<K, V> {
/**
/** * The map the keys and values are stored in.
* The map the keys and values are stored in. */
*/ protected Map<K, DefaultCache.CacheObject<V>> map;
protected Map<K, CacheObject<V>> map;
/**
/** * Linked list to maintain order that cache objects are accessed
* Linked list to maintain order that cache objects are accessed * in, most used to least used.
* in, most used to least used. */
*/ protected org.jivesoftware.util.LinkedList lastAccessedList;
protected org.jivesoftware.util.LinkedList lastAccessedList;
/**
/** * Linked list to maintain time that cache objects were initially added
* Linked list to maintain time that cache objects were initially added * to the cache, most recently added to oldest added.
* to the cache, most recently added to oldest added. */
*/ protected org.jivesoftware.util.LinkedList ageList;
protected LinkedList ageList;
/**
/** * Maximum size in bytes that the cache can grow to.
* Maximum size in bytes that the cache can grow to. */
*/ private int maxCacheSize;
private int maxCacheSize;
/**
/** * Maintains the current size of the cache in bytes.
* Maintains the current size of the cache in bytes. */
*/ private int cacheSize = 0;
private int cacheSize = 0;
/**
/** * Maximum length of time objects can exist in cache before expiring.
* Maximum length of time objects can exist in cache before expiring. */
*/ protected long maxLifetime;
protected long maxLifetime;
/**
/** * Maintain the number of cache hits and misses. A cache hit occurs every
* 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
* time the get method is called and the cache contains the requested * object. A cache miss represents the opposite occurence.<p>
* object. A cache miss represents the opposite occurence.<p> *
* * Keeping track of cache hits and misses lets one measure how efficient
* Keeping track of cache hits and misses lets one measure how efficient * the cache is; the higher the percentage of hits, the more efficient.
* the cache is; the higher the percentage of hits, the more efficient. */
*/ protected long cacheHits, cacheMisses = 0L;
protected long cacheHits, cacheMisses = 0L;
/**
/** * The name of the cache.
* The name of the cache. */
*/ private String name;
private String name;
/**
/** * Create a new default cache and specify the maximum size of for the cache in
* Create a new cache and specify the maximum size of for the cache in * bytes, and the maximum lifetime of objects.
* bytes, and the maximum lifetime of objects. *
* * @param name a name for the cache.
* @param name a name for the cache. * @param maxSize the maximum size of the cache in bytes. -1 means the cache
* @param maxSize the maximum size of the cache in bytes. -1 means the cache * has no max size.
* has no max size. * @param maxLifetime the maximum amount of time objects can exist in
* @param maxLifetime the maximum amount of time objects can exist in * cache before being deleted. -1 means objects never expire.
* cache before being deleted. -1 means objects never expire. */
*/ public DefaultCache(String name, int maxSize, long maxLifetime) {
public Cache(String name, int maxSize, long maxLifetime) { this.name = name;
this.name = name; this.maxCacheSize = maxSize;
this.maxCacheSize = maxSize; this.maxLifetime = maxLifetime;
this.maxLifetime = maxLifetime;
// Our primary data structure is a HashMap. The default capacity of 11
// Our primary data structure is a HashMap. The default capacity of 11 // is too small in almost all cases, so we set it bigger.
// is too small in almost all cases, so we set it bigger. map = new HashMap<K, CacheObject<V>>(103);
map = new HashMap<K, CacheObject<V>>(103);
lastAccessedList = new org.jivesoftware.util.LinkedList();
lastAccessedList = new LinkedList(); ageList = new org.jivesoftware.util.LinkedList();
ageList = new LinkedList(); }
}
public synchronized V put(K key, V value) {
public synchronized V put(K key, V value) { // Delete an old entry if it exists.
// Delete an old entry if it exists. V answer = remove(key);
V answer = remove(key);
int objectSize = calculateSize(value);
int objectSize = calculateSize(value);
// If the object is bigger than the entire cache, simply don't add it.
// If the object is bigger than the entire cache, simply don't add it. if (maxCacheSize > 0 && objectSize > maxCacheSize * .90) {
if (maxCacheSize > 0 && objectSize > maxCacheSize * .90) { Log.warn("Cache: " + name + " -- object with key " + key +
Log.warn("Cache: " + name + " -- object with key " + key + " is too large to fit in cache. Size is " + objectSize);
" is too large to fit in cache. Size is " + objectSize); return value;
return value; }
} cacheSize += objectSize;
cacheSize += objectSize; DefaultCache.CacheObject<V> cacheObject = new DefaultCache.CacheObject<V>(value, objectSize);
CacheObject<V> cacheObject = new CacheObject<V>(value, objectSize); map.put(key, cacheObject);
map.put(key, cacheObject); // Make an entry into the cache order list.
// Make an entry into the cache order list. LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key); // Store the cache order list entry so that we can get back to it
// Store the cache order list entry so that we can get back to it // during later lookups.
// during later lookups. cacheObject.lastAccessedListNode = lastAccessedNode;
cacheObject.lastAccessedListNode = lastAccessedNode; // Add the object to the age list
// Add the object to the age list LinkedListNode ageNode = ageList.addFirst(key);
LinkedListNode ageNode = ageList.addFirst(key); // We make an explicit call to currentTimeMillis() so that total accuracy
// We make an explicit call to currentTimeMillis() so that total accuracy // of lifetime calculations is better than one second.
// of lifetime calculations is better than one second. ageNode.timestamp = System.currentTimeMillis();
ageNode.timestamp = System.currentTimeMillis(); cacheObject.ageListNode = ageNode;
cacheObject.ageListNode = ageNode;
// If cache is too full, remove least used cache entries until it is
// If cache is too full, remove least used cache entries until it is // not too full.
// not too full. cullCache();
cullCache();
return answer;
return answer; }
}
public synchronized V get(Object key) {
public synchronized V get(Object key) { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
DefaultCache.CacheObject<V> cacheObject = map.get(key);
CacheObject<V> cacheObject = map.get(key); if (cacheObject == null) {
if (cacheObject == null) { // The object didn't exist in cache, so increment cache misses.
// The object didn't exist in cache, so increment cache misses. cacheMisses++;
cacheMisses++; return null;
return null; }
}
// The object exists in cache, so increment cache hits. Also, increment
// The object exists in cache, so increment cache hits. Also, increment // the object's read count.
// the object's read count. cacheHits++;
cacheHits++; cacheObject.readCount++;
cacheObject.readCount++;
// Remove the object from it's current place in the cache order list,
// Remove the object from it's current place in the cache order list, // and re-insert it at the front of the list.
// and re-insert it at the front of the list. cacheObject.lastAccessedListNode.remove();
cacheObject.lastAccessedListNode.remove(); lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
return cacheObject.object;
return cacheObject.object; }
}
public synchronized V remove(Object key) {
public synchronized V remove(Object key) { DefaultCache.CacheObject<V> cacheObject = map.get(key);
CacheObject<V> cacheObject = map.get(key); // If the object is not in cache, stop trying to remove it.
// If the object is not in cache, stop trying to remove it. if (cacheObject == null) {
if (cacheObject == null) { return null;
return null; }
} // remove from the hash map
// remove from the hash map map.remove(key);
map.remove(key); // remove from the cache order list
// remove from the cache order list cacheObject.lastAccessedListNode.remove();
cacheObject.lastAccessedListNode.remove(); cacheObject.ageListNode.remove();
cacheObject.ageListNode.remove(); // remove references to linked list nodes
// remove references to linked list nodes cacheObject.ageListNode = null;
cacheObject.ageListNode = null; cacheObject.lastAccessedListNode = null;
cacheObject.lastAccessedListNode = null; // removed the object, so subtract its size from the total.
// removed the object, so subtract its size from the total. cacheSize -= cacheObject.size;
cacheSize -= cacheObject.size; return cacheObject.object;
return cacheObject.object; }
}
public synchronized void clear() {
public synchronized void clear() { Object[] keys = map.keySet().toArray();
Object[] keys = map.keySet().toArray(); for (int i = 0; i < keys.length; i++) {
for (int i = 0; i < keys.length; i++) { remove(keys[i]);
remove(keys[i]); }
}
// Now, reset all containers.
// Now, reset all containers. map.clear();
map.clear(); lastAccessedList.clear();
lastAccessedList.clear(); lastAccessedList = new org.jivesoftware.util.LinkedList();
lastAccessedList = new LinkedList(); ageList.clear();
ageList.clear(); ageList = new org.jivesoftware.util.LinkedList();
ageList = new LinkedList();
cacheSize = 0;
cacheSize = 0; cacheHits = 0;
cacheHits = 0; cacheMisses = 0;
cacheMisses = 0; }
}
public int size() {
public int size() { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
return map.size();
return map.size(); }
}
public boolean isEmpty() {
public boolean isEmpty() { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
return map.isEmpty();
return map.isEmpty(); }
}
public Collection<V> values() {
public Collection<V> values() { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries(); return new DefaultCache.CacheObjectCollection(map.values());
return new CacheObjectCollection(map.values()); }
}
/**
/** * Wraps a cached object collection to return a view of its inner objects
* Wraps a cached object collection to return a view of its inner objects */
*/ private final class CacheObjectCollection<V> implements Collection<V> {
private final class CacheObjectCollection<V> implements Collection<V> { private Collection<DefaultCache.CacheObject<V>> cachedObjects;
private Collection<CacheObject<V>> cachedObjects;
private CacheObjectCollection(Collection<DefaultCache.CacheObject<V>> cachedObjects) {
private CacheObjectCollection(Collection<CacheObject<V>> cachedObjects) { this.cachedObjects = new ArrayList<CacheObject<V>>(cachedObjects);
this.cachedObjects = new ArrayList<CacheObject<V>>(cachedObjects); }
}
public int size() {
public int size() { return cachedObjects.size();
return cachedObjects.size(); }
}
public boolean isEmpty() {
public boolean isEmpty() { return size() == 0;
return size() == 0; }
}
public boolean contains(Object o) {
public boolean contains(Object o) { Iterator<V> it = iterator();
Iterator<V> it = iterator(); while (it.hasNext()) {
while (it.hasNext()) { if (it.next().equals(o)) {
if (it.next().equals(o)) { return true;
return true; }
} }
} return false;
return false; }
}
public Iterator<V> iterator() {
public Iterator<V> iterator() { return new Iterator<V>() {
return new Iterator<V>() { private final Iterator<DefaultCache.CacheObject<V>> it = cachedObjects.iterator();
private final Iterator<CacheObject<V>> it = cachedObjects.iterator();
public boolean hasNext() {
public boolean hasNext() { return it.hasNext();
return it.hasNext(); }
}
public V next() {
public V next() { if(it.hasNext()) {
if(it.hasNext()) { DefaultCache.CacheObject<V> object = it.next();
CacheObject<V> object = it.next(); if(object == null) {
if(object == null) { return null;
return null; } else {
} else { return object.object;
return object.object; }
} }
} else {
else { throw new NoSuchElementException();
throw new NoSuchElementException(); }
} }
}
public void remove() {
public void remove() { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
} };
}; }
}
public Object[] toArray() {
public Object[] toArray() { Object[] array = new Object[size()];
Object[] array = new Object[size()]; Iterator it = iterator();
Iterator it = iterator(); int i = 0;
int i = 0; while (it.hasNext()) {
while (it.hasNext()) { array[i] = it.next();
array[i] = it.next(); }
} return array;
return array; }
}
public <V>V[] toArray(V[] a) {
public <V>V[] toArray(V[] a) { Iterator<V> it = (Iterator<V>) iterator();
Iterator<V> it = (Iterator<V>) iterator(); int i = 0;
int i = 0; while (it.hasNext()) {
while (it.hasNext()) { a[i++] = it.next();
a[i++] = it.next(); }
} return a;
return a; }
}
public boolean containsAll(Collection<?> c) {
public boolean containsAll(Collection<?> c) { Iterator it = c.iterator();
Iterator it = c.iterator(); while(it.hasNext()) {
while(it.hasNext()) { if(!contains(it.next())) {
if(!contains(it.next())) { return false;
return false; }
} }
} return true;
return true; }
}
public boolean add(V o) {
public boolean add(V o) { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
}
public boolean remove(Object o) {
public boolean remove(Object o) { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
}
public boolean addAll(Collection<? extends V> coll) {
public boolean addAll(Collection<? extends V> coll) { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
}
public boolean removeAll(Collection<?> coll) {
public boolean removeAll(Collection<?> coll) { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
}
public boolean retainAll(Collection<?> coll) {
public boolean retainAll(Collection<?> coll) { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
}
public void clear() {
public void clear() { throw new UnsupportedOperationException();
throw new UnsupportedOperationException(); }
} }
}
public boolean containsKey(Object key) {
public boolean containsKey(Object key) { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
return map.containsKey(key);
return map.containsKey(key); }
}
public void putAll(Map<? extends K, ? extends V> map) {
public void putAll(Map<? extends K, ? extends V> map) { for (Iterator<? extends K> i = map.keySet().iterator(); i.hasNext();) {
for (Iterator<? extends K> i = map.keySet().iterator(); i.hasNext();) { K key = i.next();
K key = i.next(); V value = map.get(key);
V value = map.get(key); put(key, value);
put(key, value); }
} }
}
public boolean containsValue(Object value) {
public boolean containsValue(Object value) { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
if(value == null) {
if(value == null) { return containsNullValue();
return containsNullValue(); }
}
Iterator it = values().iterator();
Iterator it = values().iterator(); while(it.hasNext()) {
while(it.hasNext()) { if(value.equals(it.next())) {
if(value.equals(it.next())) { return true;
return true; }
} }
} return false;
return false; }
}
private boolean containsNullValue() {
private boolean containsNullValue() { Iterator it = values().iterator();
Iterator it = values().iterator(); while(it.hasNext()) {
while(it.hasNext()) { if(it.next() == null) {
if(it.next() == null) { return true;
return true; }
} }
} return false;
return false; }
}
public Set entrySet() {
public Set entrySet() { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries(); // TODO Make this work right
// TODO Make this work right
return Collections.unmodifiableSet(map.entrySet());
return Collections.unmodifiableSet(map.entrySet()); }
}
public Set<K> keySet() {
public Set<K> keySet() { // First, clear all entries that have been in cache longer than the
// First, clear all entries that have been in cache longer than the // maximum defined age.
// maximum defined age. deleteExpiredEntries();
deleteExpiredEntries();
return Collections.unmodifiableSet(map.keySet());
return Collections.unmodifiableSet(map.keySet()); }
}
/**
/** * Returns the name of this cache. The name is completely arbitrary
* Returns the name of this cache. The name is completely arbitrary * and used only for display to administrators.
* and used only for display to administrators. *
* * @return the name of this cache.
* @return the name of this cache. */
*/ public String getName() {
public String getName() { return name;
return name; }
}
/**
/** * Sets the name of this cache.
* Returns the number of cache hits. A cache hit occurs every *
* time the get method is called and the cache contains the requested * @param name the name of this cache.
* object.<p> */
* public void setName(String name) {
* Keeping track of cache hits and misses lets one measure how efficient this.name = name;
* the cache is; the higher the percentage of hits, the more efficient. }
*
* @return the number of cache hits. /**
*/ * Returns the number of cache hits. A cache hit occurs every
public long getCacheHits() { * time the get method is called and the cache contains the requested
return cacheHits; * 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.
* Returns the number of cache misses. A cache miss occurs every *
* time the get method is called and the cache does not contain the * @return the number of cache hits.
* requested object.<p> */
* public long getCacheHits() {
* Keeping track of cache hits and misses lets one measure how efficient return cacheHits;
* the cache is; the higher the percentage of hits, the more efficient. }
*
* @return the number of cache hits. /**
*/ * Returns the number of cache misses. A cache miss occurs every
public long getCacheMisses() { * time the get method is called and the cache does not contain the
return cacheMisses; * 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.
* Returns the size of the cache contents in bytes. This value is only a *
* rough approximation, so cache users should expect that actual VM * @return the number of cache hits.
* memory used by the cache could be significantly higher than the value */
* reported by this method. public long getCacheMisses() {
* return cacheMisses;
* @return the size of the cache contents in bytes. }
*/
public int getCacheSize() { /**
return cacheSize; * 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.
* 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 * @return the size of the cache contents in bytes.
* the max cache size is set to -1, there is no size limit. */
* public int getCacheSize() {
* @return the maximum size of the cache (-1 indicates unlimited max size). return cacheSize;
*/ }
public int getMaxCacheSize() {
return maxCacheSize; /**
} * 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.
* 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 * @return the maximum size of the cache (-1 indicates unlimited max size).
* the max cache size is set to -1, there is no size limit. */
* public int getMaxCacheSize() {
* @param maxCacheSize the maximum size of this cache (-1 indicates unlimited max size). return maxCacheSize;
*/ }
public void setMaxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize; /**
// It's possible that the new max size is smaller than our current cache * Sets the maximum size of the cache. If the cache grows larger
// size. If so, we need to delete infrequently used items. * than the max size, the least frequently used items will be removed. If
cullCache(); * 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).
/** */
* Returns the maximum number of milleseconds that any object can live public void setMaxCacheSize(int maxCacheSize) {
* in cache. Once the specified number of milleseconds passes, the object this.maxCacheSize = maxCacheSize;
* will be automatically expried from cache. If the max lifetime is set // It's possible that the new max size is smaller than our current cache
* to -1, then objects never expire. // size. If so, we need to delete infrequently used items.
* cullCache();
* @return the maximum number of milleseconds before objects are expired. }
*/
public long getMaxLifetime() { /**
return maxLifetime; * 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.
* Sets the maximum number of milleseconds that any object can live *
* in cache. Once the specified number of milleseconds passes, the object * @return the maximum number of milleseconds before objects are expired.
* will be automatically expried from cache. If the max lifetime is set */
* to -1, then objects never expire. public long getMaxLifetime() {
* return maxLifetime;
* @param maxLifetime the maximum number of milleseconds before objects are expired. }
*/
public void setMaxLifetime(long maxLifetime) { /**
this.maxLifetime = 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.
* Returns the size of an object in bytes. Determining size by serialization *
* is only used as a last resort. * @param maxLifetime the maximum number of milleseconds before objects are expired.
* */
* @return the size of an object in bytes. public void setMaxLifetime(long maxLifetime) {
*/ this.maxLifetime = maxLifetime;
private int calculateSize(Object object) { }
// If the object is Cacheable, ask it its size.
if (object instanceof Cacheable) { /**
return ((Cacheable)object).getCachedSize(); * Returns the size of an object in bytes. Determining size by serialization
} * is only used as a last resort.
// Check for other common types of objects put into cache. *
else if (object instanceof String) { * @return the size of an object in bytes.
return CacheSizes.sizeOfString((String)object); */
} private int calculateSize(Object object) {
else if (object instanceof Long) { // If the object is Cacheable, ask it its size.
return CacheSizes.sizeOfLong(); if (object instanceof Cacheable) {
} return ((Cacheable)object).getCachedSize();
else if (object instanceof Integer) { }
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt(); // Check for other common types of objects put into cache.
} else if (object instanceof String) {
else if (object instanceof Boolean) { return CacheSizes.sizeOfString((String)object);
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean(); }
} else if (object instanceof Long) {
else if (object instanceof long[]) { return CacheSizes.sizeOfLong();
long[] array = (long[])object; }
return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong(); else if (object instanceof Integer) {
} return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
else if (object instanceof byte[]) { }
byte [] array = (byte[])object; else if (object instanceof Boolean) {
return CacheSizes.sizeOfObject() + array.length; return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean();
} }
// Default behavior -- serialize the object to determine its size. else if (object instanceof long[]) {
else { long[] array = (long[])object;
int size = 1; return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong();
try { }
// Default to serializing the object out to determine size. else if (object instanceof byte[]) {
NullOutputStream out = new NullOutputStream(); byte [] array = (byte[])object;
ObjectOutputStream outObj = new ObjectOutputStream(out); return CacheSizes.sizeOfObject() + array.length;
outObj.writeObject(object); }
size = out.size(); // Default behavior -- serialize the object to determine its size.
} else {
catch (IOException ioe) { int size = 1;
Log.error(ioe); try {
} // Default to serializing the object out to determine size.
return size; DefaultCache.NullOutputStream out = new DefaultCache.NullOutputStream();
} ObjectOutputStream outObj = new ObjectOutputStream(out);
} outObj.writeObject(object);
size = out.size();
/** }
* Clears all entries out of cache where the entries are older than the catch (IOException ioe) {
* maximum defined age. Log.error(ioe);
*/ }
protected void deleteExpiredEntries() { return size;
// Check if expiration is turned on. }
if (maxLifetime <= 0) { }
return;
} /**
* Clears all entries out of cache where the entries are older than the
// Remove all old entries. To do this, we remove objects from the end * maximum defined age.
// 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 protected void deleteExpiredEntries() {
// neccessary. // Check if expiration is turned on.
LinkedListNode node = ageList.getLast(); if (maxLifetime <= 0) {
// If there are no entries in the age list, return. return;
if (node == null) { }
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
// Determine the expireTime, which is the moment in time that elements // any hash lookups or looking at any more objects than is strictly
// should expire from cache. Then, we can do an easy to check to see // neccessary.
// if the expire time is greater than the expire time. LinkedListNode node = ageList.getLast();
long expireTime = System.currentTimeMillis() - maxLifetime; // If there are no entries in the age list, return.
if (node == null) {
while (expireTime > node.timestamp) { return;
// Remove the object }
remove(node.object);
// Determine the expireTime, which is the moment in time that elements
// Get the next node. // should expire from cache. Then, we can do an easy to check to see
node = ageList.getLast(); // if the expire time is greater than the expire time.
// If there are no more entries in the age list, return. long expireTime = System.currentTimeMillis() - maxLifetime;
if (node == null) {
return; while (expireTime > node.timestamp) {
} // Remove the object
} remove(node.object);
}
// Get the next node.
/** node = ageList.getLast();
* Removes objects from cache if the cache is too full. "Too full" is // If there are no more entries in the age list, return.
* defined as within 3% of the maximum cache size. Whenever the cache is if (node == null) {
* is too big, the least frequently used elements are deleted until the return;
* cache is at least 10% empty. }
*/ }
protected final void cullCache() { }
// Check if a max cache size is defined.
if (maxCacheSize < 0) { /**
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
// See if the cache size is within 3% of being too big. If so, clean out * cache is at least 10% empty.
// cache until it's 10% free. */
if (cacheSize >= maxCacheSize * .97) { protected final void cullCache() {
// First, delete any old entries to see how much memory that frees. // Check if a max cache size is defined.
deleteExpiredEntries(); if (maxCacheSize < 0) {
int desiredSize = (int)(maxCacheSize * .90); return;
while (cacheSize > desiredSize) { }
// Get the key and invoke the remove method on it.
remove(lastAccessedList.getLast().object); // 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);
* Wrapper for all objects put into cache. It's primary purpose is to maintain while (cacheSize > desiredSize) {
* references to the linked lists that maintain the creation time of the object // Get the key and invoke the remove method on it.
* and the ordering of the most used objects. remove(lastAccessedList.getLast().object);
*/ }
private static class CacheObject<V> { }
}
/**
* Underlying object wrapped by the CacheObject. /**
*/ * Wrapper for all objects put into cache. It's primary purpose is to maintain
public V object; * references to the linked lists that maintain the creation time of the object
* and the ordering of the most used objects.
/** */
* The size of the Cacheable object. The size of the Cacheable private static class CacheObject<V> {
* 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. * Underlying object wrapped by the CacheObject.
*/ */
public int size; public V object;
/** /**
* A reference to the node in the cache order list. We keep the reference * The size of the Cacheable object. The size of the Cacheable
* here to avoid linear scans of the list. Every time the object is * object is only computed once when it is added to the cache. This makes
* accessed, the node is removed from its current spot in the list and * the assumption that once objects are added to cache, they are mostly
* moved to the front. * read-only and that their size does not change significantly over time.
*/ */
public LinkedListNode lastAccessedListNode; public int size;
/** /**
* A reference to the node in the age order list. We keep the reference * A reference to the node in the cache order list. We keep the reference
* here to avoid linear scans of the list. The reference is used if the * here to avoid linear scans of the list. Every time the object is
* object has to be deleted from the list. * accessed, the node is removed from its current spot in the list and
*/ * moved to the front.
public LinkedListNode ageListNode; */
public LinkedListNode lastAccessedListNode;
/**
* A count of the number of times the object has been read from cache. /**
*/ * A reference to the node in the age order list. We keep the reference
public int readCount = 0; * here to avoid linear scans of the list. The reference is used if the
* object has to be deleted from the list.
/** */
* Creates a new cache object wrapper. The size of the Cacheable object public LinkedListNode ageListNode;
* must be passed in in order to prevent another possibly expensive
* lookup by querying the object itself for its size.<p> /**
* * A count of the number of times the object has been read from cache.
* @param object the underlying Object to wrap. */
* @param size the size of the Cachable object in bytes. public int readCount = 0;
*/
public CacheObject(V object, int size) { /**
this.object = object; * Creates a new cache object wrapper. The size of the Cacheable object
this.size = size; * 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.
* An extension of OutputStream that does nothing but calculate the number */
* of bytes written through it. public CacheObject(V object, int size) {
*/ this.object = object;
private static class NullOutputStream extends OutputStream { this.size = size;
}
int size = 0; }
public void write(int b) throws IOException { /**
size++; * An extension of OutputStream that does nothing but calculate the number
} * of bytes written through it.
*/
public void write(byte[] b) throws IOException { private static class NullOutputStream extends OutputStream {
size += b.length;
} int size = 0;
public void write(byte[] b, int off, int len) { public void write(int b) throws IOException {
size += len; size++;
} }
/** public void write(byte[] b) throws IOException {
* Returns the number of bytes written out through the stream. size += b.length;
* }
* @return the number of bytes written to the stream.
*/ public void write(byte[] b, int off, int len) {
public int size() { size += len;
return size; }
}
} /**
} * Returns the number of bytes written out through the stream.
\ No newline at end of file *
* @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="org.jivesoftware.util.ParamUtils"%>
<%@ page import="java.text.DecimalFormat"%> <%@ page import="java.text.DecimalFormat"%>
<%-- <%--
...@@ -90,17 +90,17 @@ ...@@ -90,17 +90,17 @@
</head> </head>
<body> <body>
<% // Get parameters <% // Get parameters
boolean doClearCache = request.getParameter("clear") != null; boolean doClearCache = request.getParameter("clear") != null;
int refresh = ParamUtils.getIntParameter(request,"refresh",-1); int refresh = ParamUtils.getIntParameter(request, "refresh", -1);
int[] cacheIDs = ParamUtils.getIntParameters(request,"cacheID",-1); int[] cacheIDs = ParamUtils.getIntParameters(request, "cacheID", -1);
// Get the list of existing caches // Get the list of existing caches
Cache[] caches = webManager.getCaches(); Cache[] caches = webManager.getCaches();
// Clear one or multiple caches if requested. // Clear one or multiple caches if requested.
if (doClearCache) { if (doClearCache) {
for (int i=0; i<cacheIDs.length; i++) { for (int i = 0; i < cacheIDs.length; i++) {
caches[cacheIDs[i]].clear(); 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