Smack/smack-core/src/main/java/org/jivesoftware/smack/packet/Packet.java

399 lines
13 KiB
Java

/**
*
* Copyright 2003-2007 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.util.PacketUtil;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.util.XmppStringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
* Base class for XMPP Stanzas, which are called packets in Smack.
* <p>
* Every packet has a unique ID (which is automatically
* generated, but can be overridden). Optionally, the "to" and "from" fields can be set.
* </p>
* <p>
* XMPP Stanzas are {@link Message}, {@link IQ} and {@link Presence}. Which therefore subclass this class.
* </p>
*
* @author Matt Tucker
*/
public abstract class Packet implements TopLevelStreamElement {
public static final String TEXT = "text";
public static final String ITEM = "item";
protected static final String DEFAULT_LANGUAGE =
java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);
/**
* A prefix helps to make sure that ID's are unique across multiple instances.
*/
private static final String prefix = StringUtils.randomString(5) + "-";
/**
* Keeps track of the current increment, which is appended to the prefix to
* forum a unique ID.
*/
private static final AtomicLong id = new AtomicLong();
private final Map<String, PacketExtension> packetExtensions = new LinkedHashMap<String, PacketExtension>(12);
private String packetID = null;
private String to = null;
private String from = null;
private XMPPError error = null;
/**
* Optional value of the 'xml:lang' attribute of the outermost element of
* the stanza.
* <p>
* Such an attribute is defined for all stanza types. For IQ, see for
* example XEP-50 3.7:
* "The requester SHOULD provide its locale information using the "xml:lang
* " attribute on either the <iq/> (RECOMMENDED) or <command/> element."
* </p>
*/
protected String language;
public Packet() {
this(prefix + Long.toString(id.incrementAndGet()));
}
public Packet(String packetID) {
setPacketID(packetID);
}
public Packet(Packet p) {
packetID = p.getPacketID();
to = p.getTo();
from = p.getFrom();
error = p.error;
// Copy extensions
for (PacketExtension pe : p.getExtensions()) {
addExtension(pe);
}
}
/**
* Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
* ID_NOT_AVAILABLE was set as the packet's id.
*
* @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
*/
public String getPacketID() {
return packetID;
}
/**
* Sets the unique ID of the packet. To indicate that a packet has no id
* pass the constant ID_NOT_AVAILABLE as the packet's id value.
*
* @param packetID the unique ID for the packet.
*/
public void setPacketID(String packetID) {
this.packetID = packetID;
}
/**
* Returns who the packet is being sent "to", or <tt>null</tt> if
* the value is not set. The XMPP protocol often makes the "to"
* attribute optional, so it does not always need to be set.<p>
*
* @return who the packet is being sent to, or <tt>null</tt> if the
* value has not been set.
*/
public String getTo() {
return to;
}
/**
* Sets who the packet is being sent "to". The XMPP protocol often makes
* the "to" attribute optional, so it does not always need to be set.
*
* @param to who the packet is being sent to.
*/
public void setTo(String to) {
this.to = to;
}
/**
* Returns who the packet is being sent "from" or <tt>null</tt> if
* the value is not set. The XMPP protocol often makes the "from"
* attribute optional, so it does not always need to be set.<p>
*
* @return who the packet is being sent from, or <tt>null</tt> if the
* value has not been set.
*/
public String getFrom() {
return from;
}
/**
* Sets who the packet is being sent "from". The XMPP protocol often
* makes the "from" attribute optional, so it does not always need to
* be set.
*
* @param from who the packet is being sent to.
*/
public void setFrom(String from) {
this.from = from;
}
/**
* Returns the error associated with this packet, or <tt>null</tt> if there are
* no errors.
*
* @return the error sub-packet or <tt>null</tt> if there isn't an error.
*/
public XMPPError getError() {
return error;
}
/**
* Sets the error for this packet.
*
* @param error the error to associate with this packet.
*/
public void setError(XMPPError error) {
this.error = error;
}
/**
* Returns the xml:lang of this Stanza, or null if one has not been set.
*
* @return the xml:lang of this Stanza, or null.
*/
public String getLanguage() {
return language;
}
/**
* Sets the xml:lang of this Stanza.
*
* @param language the xml:lang of this Stanza.
*/
public void setLanguage(String language) {
this.language = language;
}
/**
* Returns a copy of the packet extensions attached to the packet.
*
* @return the packet extensions.
*/
public List<PacketExtension> getExtensions() {
synchronized (packetExtensions) {
if (packetExtensions.isEmpty()) {
return Collections.emptyList();
}
return new ArrayList<PacketExtension>(packetExtensions.values());
}
}
/**
* Returns the first extension of this packet that has the given namespace.
* <p>
* When possible, use {@link #getExtension(String,String)} instead.
* </p>
*
* @param namespace the namespace of the extension that is desired.
* @return the packet extension with the given namespace.
*/
public PacketExtension getExtension(String namespace) {
return PacketUtil.packetExtensionfromCollection(getExtensions(), null, namespace);
}
/**
* Returns the first packet extension that matches the specified element name and
* namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null,
* only the namespace is matched. Packet extensions are
* are arbitrary XML sub-documents in standard XMPP packets. By default, a
* DefaultPacketExtension instance will be returned for each extension. However,
* PacketExtensionProvider instances can be registered with the
* {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
* class to handle custom parsing. In that case, the type of the Object
* will be determined by the provider.
*
* @param elementName the XML element name of the packet extension. (May be null)
* @param namespace the XML element namespace of the packet extension.
* @return the extension, or <tt>null</tt> if it doesn't exist.
*/
@SuppressWarnings("unchecked")
public <PE extends PacketExtension> PE getExtension(String elementName, String namespace) {
if (namespace == null) {
return null;
}
String key = XmppStringUtils.generateKey(elementName, namespace);
PacketExtension packetExtension;
synchronized (packetExtensions) {
packetExtension = packetExtensions.get(key);
}
if (packetExtension == null) {
return null;
}
return (PE) packetExtension;
}
/**
* Adds a packet extension to the packet. Does nothing if extension is null.
*
* @param extension a packet extension.
*/
public void addExtension(PacketExtension extension) {
if (extension == null) return;
String key = XmppStringUtils.generateKey(extension.getElementName(), extension.getNamespace());
synchronized (packetExtensions) {
packetExtensions.put(key, extension);
}
}
/**
* Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
*
* @param extensions a collection of packet extensions
*/
public void addExtensions(Collection<PacketExtension> extensions) {
if (extensions == null) return;
for (PacketExtension packetExtension : extensions) {
addExtension(packetExtension);
}
}
/**
* Check if a packet extension with the given element and namespace exists.
* <p>
* The argument <code>elementName</code> may be null.
* </p>
*
* @param elementName
* @param namespace
* @return true if a packet extension exists, false otherwise.
*/
public boolean hasExtension(String elementName, String namespace) {
if (elementName == null) {
return hasExtension(namespace);
}
String key = XmppStringUtils.generateKey(elementName, namespace);
synchronized (packetExtensions) {
return packetExtensions.containsKey(key);
}
}
/**
* Check if a packet extension with the given namespace exists.
*
* @param namespace
* @return true if a packet extension exists, false otherwise.
*/
public boolean hasExtension(String namespace) {
synchronized (packetExtensions) {
for (PacketExtension packetExtension : packetExtensions.values()) {
if (packetExtension.getNamespace().equals(namespace)) {
return true;
}
}
}
return false;
}
/**
* Remove the packet extension with the given elementName and namespace.
*
* @param elementName
* @param namespace
* @return the removed packet extension or null.
*/
public PacketExtension removeExtension(String elementName, String namespace) {
String key = XmppStringUtils.generateKey(elementName, namespace);
synchronized (packetExtensions) {
return packetExtensions.remove(key);
}
}
/**
* Removes a packet extension from the packet.
*
* @param extension the packet extension to remove.
* @return the removed packet extension or null.
*/
public PacketExtension removeExtension(PacketExtension extension) {
return removeExtension(extension.getElementName(), extension.getNamespace());
}
/**
* Returns the extension sub-packets (including properties data) as an XML
* String, or the Empty String if there are no packet extensions.
*
* @return the extension sub-packets as XML or the Empty String if there
* are no packet extensions.
*/
protected final XmlStringBuilder getExtensionsXML() {
XmlStringBuilder xml = new XmlStringBuilder();
// Add in all standard extension sub-packets.
for (PacketExtension extension : getExtensions()) {
xml.append(extension.toXML());
}
return xml;
}
/**
* Returns the default language used for all messages containing localized content.
*
* @return the default language
*/
public static String getDefaultLanguage() {
return DEFAULT_LANGUAGE;
}
/**
* Add to, from, id and 'xml:lang' attributes
*
* @param xml
*/
protected void addCommonAttributes(XmlStringBuilder xml) {
xml.optAttribute("to", getTo());
xml.optAttribute("from", getFrom());
xml.optAttribute("id", getPacketID());
xml.xmllangAttribute(getLanguage());
}
/**
* Append an XMPPError is this packet has one set.
*
* @param xml the XmlStringBuilder to append the error to.
*/
protected void appendErrorIfExists(XmlStringBuilder xml) {
XMPPError error = getError();
if (error != null) {
xml.append(error.toXML());
}
}
}