Commit 81c91ade authored by guus's avatar guus

JID class cache not longer uses one single mutex to synchronize all access on (JM-1453).

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@10838 b35dd754-fafc-0310-a699-88a17e54d16e
parent 758b9306
...@@ -17,9 +17,10 @@ import java.io.Externalizable; ...@@ -17,9 +17,10 @@ import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
import java.io.ObjectOutput; import java.io.ObjectOutput;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/** /**
* An XMPP address (JID). A JID is made up of a node (generally a username), a domain, * An XMPP address (JID). A JID is made up of a node (generally a username), a domain,
...@@ -46,7 +47,7 @@ public class JID implements Comparable<JID>, Externalizable { ...@@ -46,7 +47,7 @@ public class JID implements Comparable<JID>, Externalizable {
// Stringprep operations are very expensive. Therefore, we cache node, domain and // Stringprep operations are very expensive. Therefore, we cache node, domain and
// resource values that have already had stringprep applied so that we can check // resource values that have already had stringprep applied so that we can check
// incoming values against the cache. // incoming values against the cache.
private static Map<String,Object> stringprepCache = Collections.synchronizedMap(new Cache(10000)); private static Cache<String> stringprepCache = new Cache<String>(10000);
private String node; private String node;
private String domain; private String domain;
...@@ -198,13 +199,13 @@ public class JID implements Comparable<JID>, Externalizable { ...@@ -198,13 +199,13 @@ public class JID implements Comparable<JID>, Externalizable {
public static String resourceprep(String resource) throws StringprepException { public static String resourceprep(String resource) throws StringprepException {
String answer = resource; String answer = resource;
if (!stringprepCache.containsKey(resource)) { if (!stringprepCache.contains(resource)) {
answer = Stringprep.resourceprep(resource); answer = Stringprep.resourceprep(resource);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (answer != null && answer.length()*2 > 1023) { if (answer != null && answer.length()*2 > 1023) {
return answer; return answer;
} }
stringprepCache.put(answer, null); stringprepCache.put(answer);
} }
return answer; return answer;
} }
...@@ -346,14 +347,14 @@ public class JID implements Comparable<JID>, Externalizable { ...@@ -346,14 +347,14 @@ public class JID implements Comparable<JID>, Externalizable {
} }
// Stringprep (node prep, resourceprep, etc). // Stringprep (node prep, resourceprep, etc).
try { try {
if (!stringprepCache.containsKey(node)) { if (!stringprepCache.contains(node)) {
this.node = Stringprep.nodeprep(node); this.node = Stringprep.nodeprep(node);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (this.node != null && this.node.length()*2 > 1023) { if (this.node != null && this.node.length()*2 > 1023) {
throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " + throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " +
"Size is " + (this.node.length() * 2) + " bytes."); "Size is " + (this.node.length() * 2) + " bytes.");
} }
stringprepCache.put(this.node, null); stringprepCache.put(this.node);
} }
else { else {
this.node = node; this.node = node;
...@@ -362,14 +363,14 @@ public class JID implements Comparable<JID>, Externalizable { ...@@ -362,14 +363,14 @@ public class JID implements Comparable<JID>, Externalizable {
// that they should be run through nameprep before doing any // that they should be run through nameprep before doing any
// comparisons. We always run the domain through nameprep to // comparisons. We always run the domain through nameprep to
// make comparisons easier later. // make comparisons easier later.
if (!stringprepCache.containsKey(domain)) { if (!stringprepCache.contains(domain)) {
this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false); this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (this.domain.length()*2 > 1023) { if (this.domain.length()*2 > 1023) {
throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " + throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " +
"Size is " + (this.domain.length() * 2) + " bytes."); "Size is " + (this.domain.length() * 2) + " bytes.");
} }
stringprepCache.put(this.domain, null); stringprepCache.put(this.domain);
} }
else { else {
this.domain = domain; this.domain = domain;
...@@ -537,20 +538,75 @@ public class JID implements Comparable<JID>, Externalizable { ...@@ -537,20 +538,75 @@ public class JID implements Comparable<JID>, Externalizable {
} }
/** /**
* A simple cache class that extends LinkedHashMap. It uses an LRU policy to * A simple cache class with limited functionality. It uses an FIFO
* keep the cache at a maximum size. * eviction policy to keep the cache at a maximum size. This class
* offers acceptable thread safety for the purpose of the parent class.
*
* @author Guus der Kinderen, guus@nimbuzz.com
*/ */
private static class Cache extends LinkedHashMap<String,Object> { private static class Cache<K> {
/** Cannot add null values in ConcurrentHashMap... */
private final static Object NULL = new Object();
/** Queue that records insertion order. Used to implement FIFO behavior. */
private final Queue<K> fifoQueue = new ConcurrentLinkedQueue<K>();
/** Values are cached in a hashmap for fast lookup. **/
private final Map<K, Object> cachedValues = new ConcurrentHashMap<K, Object>();
/** Cache capacity */
private int maxSize; private int maxSize;
/**
* Constructs a new capacity-bound cache.
*
* @param maxSize
* The maximum number of elements that the cache can contain.
*/
public Cache(int maxSize) { public Cache(int maxSize) {
super(64, .75f, true);
this.maxSize = maxSize; this.maxSize = maxSize;
} }
protected boolean removeEldestEntry(Map.Entry eldest) { /**
return size() > maxSize; * Adds a new element to the cache. The cache is pruned if the maximum
* capacity has been reached.
*
* @param entry
* The element to be added to the cache
*/
public void put(K entry) {
synchronized (entry) {
// add value to the cache
if (cachedValues.put(entry, NULL) != null) {
// ensure that queue doesn't contain duplicates.
fifoQueue.offer(entry);
}
}
// apply eviction policy if required.
while (cachedValues.size() > maxSize) {
cachedValues.remove(fifoQueue.poll());
}
}
/**
* Checks if the cache contains an element.
*
* @param entry
* The element to check for.
* @return <tt>true</tt> if the cache currently contains the entry,
* <tt>false</tt> otherwise.
*/
public boolean contains(K entry) {
// no need to nodeprep null - it'll result in null.
if (entry == null) {
return true;
}
// Note that this method will need to record access if
// we want to switch to LRU.
return cachedValues.containsKey(entry);
} }
} }
......
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