Commit 91c8f856 authored by Tom Evans's avatar Tom Evans Committed by akrherz

OF-921: Cache group names rather than instances

Improves GroupAwareMap and GroupAwareList implementations by caching
group names (in lieu of group instances) that are referenced in the
corresponding collection. This enables rapid group retrieval from
primary cache where applicable.
parent 74097069
...@@ -16,13 +16,14 @@ import org.xmpp.packet.JID; ...@@ -16,13 +16,14 @@ import org.xmpp.packet.JID;
*/ */
public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements GroupAwareList<T> { public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements GroupAwareList<T> {
private static final long serialVersionUID = -8884698048047935327L; private static final long serialVersionUID = 7735849143650412115L;
// This set is used to optimize group operations within this list. // This set is used to optimize group operations within this list.
// We only populate this set when it's needed to dereference the // We only populate this set when it's needed to dereference the
// groups in the base list, but once it exists we keep it in sync // groups in the base list, but once it exists we keep it in sync
// via the various add/remove operations. // via the various add/remove operations.
private transient Set<Group> groupsInList; // NOTE: added volatile keyword for double-check idiom (lazy instantiation)
private volatile transient Set<String> knownGroupNamesInList;
public ConcurrentGroupList() { public ConcurrentGroupList() {
super(); super();
...@@ -61,20 +62,41 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -61,20 +62,41 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
* @return A Set containing the groups in the list * @return A Set containing the groups in the list
*/ */
@Override @Override
public synchronized Set<Group> getGroups() { public Set<Group> getGroups() {
if (groupsInList == null) { Set<Group> result = new HashSet<Group>();
groupsInList = new HashSet<Group>(); for (String groupName : getKnownGroupNamesInList()) {
// add all the groups into the group set result.add(Group.resolveFrom(groupName));
Iterator<T> iterator = iterator(); }
while (iterator.hasNext()) { return result;
T listItem = iterator.next(); }
Group group = Group.resolveFrom(listItem);
if (group != null) { /**
groupsInList.add(group); * Accessor uses the "double-check idiom" (j2se 5.0+) for proper lazy instantiation.
}; * Additionally, the set is not cached until there is at least one group in the list.
*
* @return the known group names among the items in the list
*/
private Set<String> getKnownGroupNamesInList() {
Set<String> result = knownGroupNamesInList;
if (result == null) {
synchronized(this) {
result = knownGroupNamesInList;
if (result == null) {
result = new HashSet<String>();
// add all the groups into the group set
Iterator<T> iterator = iterator();
while (iterator.hasNext()) {
T listItem = iterator.next();
Group group = Group.resolveFrom(listItem);
if (group != null) {
result.add(group.getName());
};
}
knownGroupNamesInList = result.isEmpty() ? null : result;
}
} }
} }
return groupsInList; return result;
} }
/** /**
...@@ -88,14 +110,17 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -88,14 +110,17 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
private synchronized boolean syncGroups(Object item, boolean addOrRemove) { private synchronized boolean syncGroups(Object item, boolean addOrRemove) {
boolean result = false; boolean result = false;
// only sync if the group list has been instantiated // only sync if the group list has been instantiated
if (groupsInList != null) { if (knownGroupNamesInList != null) {
Group group = Group.resolveFrom(item); Group group = Group.resolveFrom(item);
if (group != null) { if (group != null) {
result = true; result = true;
if (addOrRemove == ADD) { if (addOrRemove == ADD) {
groupsInList.add(group); knownGroupNamesInList.add(group.getName());
} else if (addOrRemove == REMOVE) { } else if (addOrRemove == REMOVE) {
groupsInList.remove(group); knownGroupNamesInList.remove(group.getName());
if (knownGroupNamesInList.isEmpty()) {
knownGroupNamesInList = null;
}
} }
} }
} }
...@@ -155,7 +180,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -155,7 +180,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
if (changed) { if (changed) {
// drop the transient set, will be rebuilt when/if needed // drop the transient set, will be rebuilt when/if needed
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
return changed; return changed;
...@@ -167,7 +192,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -167,7 +192,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
if (changed) { if (changed) {
// drop the transient set, will be rebuilt when/if needed // drop the transient set, will be rebuilt when/if needed
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
return changed; return changed;
...@@ -179,7 +204,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -179,7 +204,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
if (added > 0) { if (added > 0) {
// drop the transient set, will be rebuilt when/if needed // drop the transient set, will be rebuilt when/if needed
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
return added; return added;
...@@ -189,7 +214,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -189,7 +214,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
public void clear() { public void clear() {
super.clear(); super.clear();
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
...@@ -199,7 +224,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -199,7 +224,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
if (changed) { if (changed) {
// drop the transient set, will be rebuilt when/if needed // drop the transient set, will be rebuilt when/if needed
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
return changed; return changed;
...@@ -211,7 +236,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G ...@@ -211,7 +236,7 @@ public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements G
if (changed) { if (changed) {
// drop the transient set, will be rebuilt when/if needed // drop the transient set, will be rebuilt when/if needed
synchronized(this) { synchronized(this) {
groupsInList = null; knownGroupNamesInList = null;
} }
} }
return changed; return changed;
......
...@@ -6,8 +6,6 @@ import java.util.Map; ...@@ -6,8 +6,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.openfire.event.GroupEventListener;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
/** /**
...@@ -17,28 +15,22 @@ import org.xmpp.packet.JID; ...@@ -17,28 +15,22 @@ import org.xmpp.packet.JID;
* @author Tom Evans * @author Tom Evans
*/ */
public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implements GroupAwareMap<K, V>, GroupEventListener { public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implements GroupAwareMap<K, V> {
private static final long serialVersionUID = -2068242013524715293L; private static final long serialVersionUID = 5479781418678223200L;
// These sets are used to optimize group operations within this map. // These sets are used to optimize group operations within this map.
// We only populate these sets when they are needed to dereference the // We only populate these sets when they are needed to dereference the
// groups in the base map, but once they exist we keep them in sync // groups in the base map, but once they exist we keep them in sync
// via the various put/remove operations. // via the various put/remove operations.
private transient Set<Group> groupsFromKeys; // NOTE: added volatile keyword for double-check idiom (lazy instantiation)
private transient Set<Group> groupsFromValues; private volatile transient Set<String> knownGroupNamesFromKeys;
private volatile transient Set<String> knownGroupNamesFromValues;
/**
* Register this instance with the GroupEventDispatcher
*/
public ConcurrentGroupMap () {
GroupEventDispatcher.addListener(this);
}
/** /**
* Returns true if the key list contains the given JID. If the JID * Returns true if the key list contains the given JID. If the JID is not found in the
* is not found in the key list, search the key list for groups and * key list (exact match), search the key list for groups and look for the JID in
* look for the JID in each of the corresponding groups. * each of the corresponding groups (implied match).
* *
* @param o The target, presumably a JID * @param o The target, presumably a JID
* @return True if the target is in the key list, or in any groups in the key list * @return True if the target is in the key list, or in any groups in the key list
...@@ -60,9 +52,9 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -60,9 +52,9 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
/** /**
* Returns true if the map has an entry value matching the given JID. If the JID * Returns true if the map has an entry value matching the given JID. If the JID is not
* is not found explicitly, search the values for groups and search * found in the value set (exact match), search the value set for groups and look for the
* for the JID in each of the corresponding groups. * JID in each of the corresponding groups (implied match).
* *
* @param value The target, presumably a JID * @param value The target, presumably a JID
* @return True if the target is in the value set, or in any groups in the value set * @return True if the target is in the value set, or in any groups in the value set
...@@ -72,7 +64,7 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -72,7 +64,7 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
if (containsValue(value)) { if (containsValue(value)) {
found = true; found = true;
} else if (value instanceof JID) { } else if (value instanceof JID) {
// look for group JIDs in the list of keys and dereference as needed // look for group JIDs in the list of values and dereference as needed
JID target = (JID) value; JID target = (JID) value;
Iterator<Group> iterator = getGroupsFromValues().iterator(); Iterator<Group> iterator = getGroupsFromValues().iterator();
while (!found && iterator.hasNext()) { while (!found && iterator.hasNext()) {
...@@ -89,19 +81,11 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -89,19 +81,11 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
*/ */
@Override @Override
public synchronized Set<Group> getGroupsFromKeys() { public synchronized Set<Group> getGroupsFromKeys() {
if (groupsFromKeys == null) { Set<Group> result = new HashSet<Group>();
groupsFromKeys = new HashSet<Group>(); for(String groupName : getKnownGroupNamesFromKeys()) {
// add all the groups into the group set result.add(Group.resolveFrom(groupName));
Iterator<K> iterator = keySet().iterator();
while (iterator.hasNext()) {
K key = iterator.next();
Group group = Group.resolveFrom(key);
if (group != null) {
groupsFromKeys.add(group);
};
}
} }
return groupsFromKeys; return result;
} }
/** /**
...@@ -111,19 +95,71 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -111,19 +95,71 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
*/ */
@Override @Override
public synchronized Set<Group> getGroupsFromValues() { public synchronized Set<Group> getGroupsFromValues() {
if (groupsFromValues == null) { Set<Group> result = new HashSet<Group>();
groupsFromValues = new HashSet<Group>(); for(String groupName : getKnownGroupNamesFromValues()) {
// add all the groups into the group set result.add(Group.resolveFrom(groupName));
Iterator<V> iterator = values().iterator(); }
while (iterator.hasNext()) { return result;
V value = iterator.next(); }
Group group = Group.resolveFrom(value);
if (group != null) {
groupsFromValues.add(group); /**
}; * Accessor uses the "double-check idiom" (j2se 5.0+) for proper lazy instantiation.
* Additionally, nothing is cached until there is at least one group in the map's keys.
*
* @return the known group names among the items in the list
*/
private Set<String> getKnownGroupNamesFromKeys() {
Set<String> result = knownGroupNamesFromKeys;
if (result == null) {
synchronized(this) {
result = knownGroupNamesFromKeys;
if (result == null) {
result = new HashSet<String>();
// add all the groups into the group set
Iterator<K> iterator = keySet().iterator();
while (iterator.hasNext()) {
K key = iterator.next();
Group group = Group.resolveFrom(key);
if (group != null) {
result.add(group.getName());
};
}
knownGroupNamesFromKeys = result.isEmpty() ? null : result;
}
}
}
return result;
}
/**
* Accessor uses the "double-check idiom" (j2se 5.0+) for proper lazy instantiation.
* Additionally, nothing is cached until there is at least one group in the map's value set.
*
* @return the known group names among the items in the list
*/
private Set<String> getKnownGroupNamesFromValues() {
Set<String> result = knownGroupNamesFromValues;
if (result == null) {
synchronized(this) {
result = knownGroupNamesFromValues;
if (result == null) {
result = new HashSet<String>();
// add all the groups into the group set
Iterator<V> iterator = values().iterator();
while (iterator.hasNext()) {
V key = iterator.next();
Group group = Group.resolveFrom(key);
if (group != null) {
result.add(group.getName());
};
}
knownGroupNamesFromValues = result.isEmpty() ? null : result;
}
} }
} }
return groupsFromValues; return result;
} }
/** /**
...@@ -137,16 +173,23 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -137,16 +173,23 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
*/ */
private synchronized boolean syncGroups(Object item, boolean keyOrValue, boolean addOrRemove) { private synchronized boolean syncGroups(Object item, boolean keyOrValue, boolean addOrRemove) {
boolean result = false; boolean result = false;
Set<Group> groupSet = (keyOrValue == KEYS) ? groupsFromKeys : groupsFromValues; Set<String> groupSet = (keyOrValue == KEYS) ? knownGroupNamesFromKeys : knownGroupNamesFromValues;
// only sync if the group list has been instantiated // only sync if the group list has been instantiated
if (groupSet != null) { if (groupSet != null) {
Group group = Group.resolveFrom(item); Group group = Group.resolveFrom(item);
if (group != null) { if (group != null) {
result = true; result = true;
if (addOrRemove == ADD) { if (addOrRemove == ADD) {
groupSet.add(group); groupSet.add(group.getName());
} else if (addOrRemove == REMOVE) { } else if (addOrRemove == REMOVE) {
groupSet.remove(group); groupSet.remove(group.getName());
if (groupSet.isEmpty()) {
if (keyOrValue == KEYS) {
knownGroupNamesFromKeys = null;
} else {
knownGroupNamesFromValues = null;
}
}
} }
} }
} }
...@@ -250,8 +293,8 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -250,8 +293,8 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
* cache would be referring to an orphaned Group instance. * cache would be referring to an orphaned Group instance.
*/ */
private synchronized void clearCache() { private synchronized void clearCache() {
groupsFromKeys = null; knownGroupNamesFromKeys = null;
groupsFromValues = null; knownGroupNamesFromValues = null;
} }
private static final boolean KEYS = true; private static final boolean KEYS = true;
...@@ -259,41 +302,4 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement ...@@ -259,41 +302,4 @@ public class ConcurrentGroupMap<K, V> extends ConcurrentHashMap<K, V> implement
private static final boolean ADD = true; private static final boolean ADD = true;
private static final boolean REMOVE = false; private static final boolean REMOVE = false;
// Implement GroupEventListener to clear Group caches as appropriate (OF-921)
@Override
public void groupCreated(Group group, Map params) {
clearCache();
}
@Override
public void groupModified(Group group, Map params) {
clearCache();
}
@Override
public void groupDeleting(Group group, Map params) {
clearCache();
}
@Override
public void memberAdded(Group group, Map params) {
clearCache();
}
@Override
public void memberRemoved(Group group, Map params) {
clearCache();
}
@Override
public void adminAdded(Group group, Map params) {
clearCache();
}
@Override
public void adminRemoved(Group group, Map params) {
clearCache();
}
} }
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