org.jivesoftware.openfire.group.GroupJID Maven / Gradle / Ivy
The newest version!
package org.jivesoftware.openfire.group;
import java.nio.charset.StandardCharsets;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* This class is designed to identify and manage custom JIDs
* that represent Groups (rather than Users or Components).
*
* The node for a GroupJID is the group name encoded as base32hex.
* This allows us to preserve special characters and upper/lower casing
* within the group name. The encoded group name is valid according to
* the RFC6122 rules for a valid node and does not require further
* JID escaping.
*
* We use an MD5 hash of the group name as the resource value to help
* distinguish Group JIDs from regular JIDs in the local domain when
* they are persisted in the DB or over the network.
*
* @author Tom Evans
*
*/
public class GroupJID extends JID {
private static final Logger Log = LoggerFactory.getLogger(GroupJID.class);
private static final long serialVersionUID = 5681300465012974014L;
private transient String groupName;
/**
* Construct a JID representing a Group.
*
* @param name A group name for the local domain
*/
public GroupJID(String name) {
super(encodeNode(name),
XMPPServer.getInstance().getServerInfo().getXMPPDomain(),
StringUtils.hash(name),
true);
groupName = name;
}
/**
* Construct a JID representing a Group from a regular JID. This constructor is
* private because it is used only from within this class after the source JID
* has been validated.
*
* @param source A full JID representing a group
* @see GroupJID#fromString
*/
private GroupJID(JID source) {
// skip stringprep for the new group JID, since it has already been parsed
super(source.getNode(), source.getDomain(), source.getResource(), true);
}
/**
* Returns the group name corresponding to this JID.
*
* @return The name for the corresponding group
*/
public String getGroupName() {
// lazy instantiation
if (groupName == null) {
groupName = decodeNode(getNode());
}
return groupName;
}
/**
* Override the base class implementation to retain the resource
* identifier for group JIDs.
*
* @return This JID, as a group JID
*/
@Override
public JID asBareJID() {
return this;
}
/**
* Override the base class implementation to retain the resource
* identifier for group JIDs.
*
* @return The full JID rendered as a string
*/
@Override
public String toBareJID() {
return this.toString();
}
@Override
public int compareTo(JID jid) {
// Comparison order is domain, node, resource.
int compare = getDomain().compareTo(jid.getDomain());
if (compare == 0) {
String otherNode = jid.getNode();
compare = otherNode == null ? 1 : getGroupName().compareTo(otherNode);
}
if (compare == 0) {
compare = jid.getResource() == null ? 0 : -1;
}
return compare;
}
/**
* Encode the given group name in base32hex (UTF-8). This encoding
* is valid according to the nodeprep profile of stringprep
* (RFC6122, Appendix A) and needs no further escaping.
*
* @param name A group name
* @return The encoded group name
*/
private static String encodeNode(String name) {
return StringUtils.encodeBase32(name);
}
/**
* Decode the given group name from base32hex (UTF-8).
*
* @param name A group name, encoded as base32hex
* @return The group name
*/
private static String decodeNode(String node) {
return new String(StringUtils.decodeBase32(node), StandardCharsets.UTF_8);
}
/**
* Check a JID to determine whether it represents a group. If the given
* JID is an instance of this class, it is a group JID. Otherwise,
* calculate the hash to determine whether the JID can be resolved to
* a group.
*
* @param jid A JID, possibly representing a group
* @return true if the given jid represents a group in the local domain
*/
public static boolean isGroup(JID jid) {
try {
return isGroup(jid, false);
} catch (GroupNotFoundException gnfe) {
// should not happen because we do not validate the group exists
Log.error("Unexpected group validation", gnfe);
return false;
}
}
/**
* Check a JID to determine whether it represents a group. If the given
* JID is an instance of this class, it is a group JID. Otherwise,
* calculate the hash to determine whether the JID can be resolved to
* a group. This method also optionally validates that the corresponding
* group actually exists in the local domain.
*
* @param jid A JID, possibly representing a group
* @param groupMustExist If true, validate that the corresponding group actually exists
* @return true if the given jid represents a group in the local domain
* @throws GroupNotFoundException The JID represents a group, but the group does not exist
*/
public static boolean isGroup(JID jid, boolean groupMustExist) throws GroupNotFoundException {
boolean isGroup = false;
String groupName = null, node = jid.getNode();
if (node != null) {
isGroup = (jid instanceof GroupJID) ? true :
jid.getResource() != null &&
StringUtils.isBase32(node) &&
StringUtils.hash(groupName = decodeNode(node)).equals(jid.getResource());
if (isGroup && groupMustExist) {
Log.debug("Validating group: " + jid);
if (XMPPServer.getInstance().isLocal(jid)) {
GroupManager.getInstance().getGroup(groupName);
} else {
isGroup = false; // not in the local domain
}
}
}
return isGroup;
}
/**
* Returns a JID from the given JID. If the JID represents a group,
* returns an instance of this class. Otherwise returns the given JID.
*
* @param jid A JID, possibly representing a group
* @return A new GroupJID if the given JID represents a group, or the given JID
*/
public static JID fromJID(JID jid) {
if (jid instanceof GroupJID || jid.getResource() == null || jid.getNode() == null) {
return jid;
} else {
return (isGroup(jid)) ? new GroupJID(jid) : jid;
}
}
/**
* Creates a JID from the given string. If the string represents a group,
* return an instance of this class. Otherwise returns a regular JID.
*
* @param jid A JID, possibly representing a group
* @return A JID with a type appropriate to its content
* @throws IllegalArgumentException the given string is not a valid JID
*/
public static JID fromString(String jid) {
Log.debug("Parsing JID from string: " + jid);
return fromJID(new JID(jid));
}
}