org.jivesoftware.openfire.group.ConcurrentGroupMap Maven / Gradle / Ivy
The newest version!
package org.jivesoftware.openfire.group;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.xmpp.packet.JID;
/**
* This extension class provides additional methods that understand groups among
* the entries in the map.
*
* @author Tom Evans
*/
public class ConcurrentGroupMap extends ConcurrentHashMap implements GroupAwareMap {
private static final long serialVersionUID = 5479781418678223200L;
// These sets are used to optimize group operations within this map.
// 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
// via the various put/remove operations.
// NOTE: added volatile keyword for double-check idiom (lazy instantiation)
private volatile transient Set knownGroupNamesFromKeys;
private volatile transient Set knownGroupNamesFromValues;
/**
* Returns true if the key list contains the given JID. If the JID is not found in the
* key list (exact match), search the key list for groups and look for the JID in
* each of the corresponding groups (implied match).
*
* @param key The target, presumably a JID
* @return True if the target is in the key list, or in any groups in the key list
*/
@Override
public boolean includesKey(Object key) {
boolean found = false;
if (containsKey(key)) {
found = true;
} else if (key instanceof JID) {
// look for group JIDs in the list of keys and dereference as needed
JID target = (JID) key;
Iterator iterator = getGroupsFromKeys().iterator();
while (!found && iterator.hasNext()) {
Group next = iterator.next();
if(next != null)
found = next.isUser(target);
}
}
return found;
}
/**
* Returns true if the map has an entry value matching the given JID. If the JID is not
* found in the value set (exact match), search the value set for groups and look for the
* JID in each of the corresponding groups (implied match).
*
* @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
*/
@Override
public boolean includesValue(Object value) {
boolean found = false;
if (containsValue(value)) {
found = true;
} else if (value instanceof JID) {
// look for group JIDs in the list of values and dereference as needed
JID target = (JID) value;
Iterator iterator = getGroupsFromValues().iterator();
while (!found && iterator.hasNext()) {
found = iterator.next().isUser(target);
}
}
return found;
}
/**
* Returns the groups that are implied (resolvable) from the keys in the map.
*
* @return A Set containing the groups among the keys
*/
@Override
public synchronized Set getGroupsFromKeys() {
Set result = new HashSet<>();
for(String groupName : getKnownGroupNamesFromKeys()) {
Group resolved = Group.resolveFrom(groupName);
if(resolved != null)
result.add(resolved);
}
return result;
}
/**
* Returns the groups that are implied (resolvable) from the values in the map.
*
* @return A Set containing the groups among the values
*/
@Override
public synchronized Set getGroupsFromValues() {
Set result = new HashSet<>();
for(String groupName : getKnownGroupNamesFromValues()) {
result.add(Group.resolveFrom(groupName));
}
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 keys.
*
* @return the known group names among the items in the list
*/
private Set getKnownGroupNamesFromKeys() {
Set result = knownGroupNamesFromKeys;
if (result == null) {
synchronized(this) {
result = knownGroupNamesFromKeys;
if (result == null) {
result = new HashSet<>();
// add all the groups into the group set
Iterator iterator = ((ConcurrentMap) this).keySet().iterator(); // Cast prevents Java compatibility issue as described in OF-1116. Remove the cast when Java 7 support is dropped from Openfire.
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 getKnownGroupNamesFromValues() {
Set result = knownGroupNamesFromValues;
if (result == null) {
synchronized(this) {
result = knownGroupNamesFromValues;
if (result == null) {
result = new HashSet<>();
// add all the groups into the group set
Iterator 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 result;
}
/**
* This method is called from several of the mutators to keep
* the group set in sync with the keys in the map.
*
* @param item The item to be added or removed if it is in the group set
* @param keyOrValue True for keys, false for values
* @param addOrRemove True to add, false to remove
* @return true if the given item is a group
*/
private synchronized boolean syncGroups(Object item, boolean keyOrValue, boolean addOrRemove) {
boolean result = false;
Set groupSet = (keyOrValue == KEYS) ? knownGroupNamesFromKeys : knownGroupNamesFromValues;
// only sync if the group list has been instantiated
if (groupSet != null) {
Group group = Group.resolveFrom(item);
if (group != null) {
result = true;
if (addOrRemove == ADD) {
groupSet.add(group.getName());
} else if (addOrRemove == REMOVE) {
groupSet.remove(group.getName());
if (groupSet.isEmpty()) {
if (keyOrValue == KEYS) {
knownGroupNamesFromKeys = null;
} else {
knownGroupNamesFromValues = null;
}
}
}
}
}
return result;
}
// below are overrides for the various mutators
@Override
public V put(K key, V value) {
V priorValue = super.put(key, value);
syncGroups(value, VALUES, ADD);
if (priorValue == null) {
syncGroups(key, KEYS, ADD);
} else {
syncGroups(priorValue, VALUES, REMOVE);
}
return priorValue;
}
@Override
public V putIfAbsent(K key, V value) {
V priorValue = super.putIfAbsent(key, value);
// if the map already contains the key, there was no change
if (!value.equals(priorValue)) {
syncGroups(value, VALUES, ADD);
if (priorValue == null) {
syncGroups(key, KEYS, ADD);
} else {
syncGroups(priorValue, VALUES, REMOVE);
}
}
return priorValue;
}
@Override
public void putAll(Map extends K, ? extends V> m) {
super.putAll(m);
// drop the transient sets; will be rebuilt when/if needed
clearCache();
}
@Override
public V remove(Object key) {
V priorValue = super.remove(key);
if (priorValue != null) {
syncGroups(key, KEYS, REMOVE);
syncGroups(priorValue, VALUES, REMOVE);
}
return priorValue;
}
@Override
public boolean remove(Object key, Object value) {
boolean removed = super.remove(key, value);
if (removed) {
syncGroups(key, KEYS, REMOVE);
syncGroups(value, VALUES, REMOVE);
}
return removed;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
boolean replaced = super.replace(key, oldValue, newValue);
if (replaced) {
syncGroups(oldValue, VALUES, REMOVE);
syncGroups(newValue, VALUES, ADD);
}
return replaced;
}
@Override
public V replace(K key, V value) {
V priorValue = super.replace(key, value);
if (priorValue != null) {
syncGroups(value, VALUES, ADD);
syncGroups(priorValue, VALUES, REMOVE);
}
return priorValue;
}
@Override
public void clear() {
super.clear();
clearCache();
}
/**
* Certain operations imply that our locally cached group list(s) should
* be dropped and recreated. For example, when an ad-hoc client command
* is used to add a member to a group, the underlying group instance is
* actually dropped from the global Group cache, with the effect that our
* cache would be referring to an orphaned Group instance.
*/
private synchronized void clearCache() {
knownGroupNamesFromKeys = null;
knownGroupNamesFromValues = null;
}
private static final boolean KEYS = true;
private static final boolean VALUES = false;
private static final boolean ADD = true;
private static final boolean REMOVE = false;
}