Stanza.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smack.packet;

  18. import static org.jivesoftware.smack.util.StringUtils.requireNotNullOrEmpty;

  19. import org.jivesoftware.smack.packet.id.StanzaIdUtil;
  20. import org.jivesoftware.smack.util.MultiMap;
  21. import org.jivesoftware.smack.util.PacketUtil;
  22. import org.jivesoftware.smack.util.XmlStringBuilder;
  23. import org.jxmpp.jid.Jid;
  24. import org.jxmpp.jid.impl.JidCreate;
  25. import org.jxmpp.stringprep.XmppStringprepException;
  26. import org.jxmpp.util.XmppStringUtils;

  27. import java.util.Collection;
  28. import java.util.List;
  29. import java.util.Locale;
  30. import java.util.Set;

  31. /**
  32.  * Base class for XMPP Stanzas, which are called Packet in older versions of Smack (i.e. < 4.1).
  33.  * <p>
  34.  * Every stanza has a unique ID (which is automatically generated, but can be overridden). Stanza
  35.  * IDs are required for IQ stanzas and recommended for presence and message stanzas. Optionally, the
  36.  * "to" and "from" fields can be set.
  37.  * </p>
  38.  * <p>
  39.  * XMPP Stanzas are {@link Message}, {@link IQ} and {@link Presence}. Which therefore subclass this
  40.  * class. <b>If you think you need to subclass this class, then you are doing something wrong.</b>
  41.  * </p>
  42.  *
  43.  * @author Matt Tucker
  44.  * @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas">RFC 6120 ยง 8. XML Stanzas</a>
  45.  */
  46. public abstract class Stanza implements TopLevelStreamElement {

  47.     public static final String TEXT = "text";
  48.     public static final String ITEM = "item";

  49.     protected static final String DEFAULT_LANGUAGE =
  50.             java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);

  51.     private final MultiMap<String, ExtensionElement> packetExtensions = new MultiMap<>();

  52.     private String id = null;
  53.     private Jid to;
  54.     private Jid from;
  55.     private XMPPError error = null;

  56.     /**
  57.      * Optional value of the 'xml:lang' attribute of the outermost element of
  58.      * the stanza.
  59.      * <p>
  60.      * Such an attribute is defined for all stanza types. For IQ, see for
  61.      * example XEP-50 3.7:
  62.      * "The requester SHOULD provide its locale information using the "xml:lang
  63.      * " attribute on either the <iq/> (RECOMMENDED) or <command/> element."
  64.      * </p>
  65.      */
  66.     protected String language;

  67.     protected Stanza() {
  68.         this(StanzaIdUtil.newStanzaId());
  69.     }

  70.     protected Stanza(String stanzaId) {
  71.         setStanzaId(stanzaId);
  72.     }

  73.     protected Stanza(Stanza p) {
  74.         id = p.getStanzaId();
  75.         to = p.getTo();
  76.         from = p.getFrom();
  77.         error = p.error;

  78.         // Copy extensions
  79.         for (ExtensionElement pe : p.getExtensions()) {
  80.             addExtension(pe);
  81.         }
  82.     }

  83.     /**
  84.      * Returns the unique ID of the stanza. The returned value could be <code>null</code>.
  85.      *
  86.      * @return the packet's unique ID or <code>null</code> if the id is not available.
  87.      */
  88.     public String getStanzaId() {
  89.         return id;
  90.     }

  91.     /**
  92.      *
  93.      * @return the stanza id.
  94.      * @deprecated use {@link #getStanzaId()} instead.
  95.      */
  96.     @Deprecated
  97.     public String getPacketID() {
  98.         return getStanzaId();
  99.     }

  100.     /**
  101.      * Sets the unique ID of the packet. To indicate that a packet has no id
  102.      * pass <code>null</code> as the packet's id value.
  103.      *
  104.      * @param id the unique ID for the packet.
  105.      */
  106.     public void setStanzaId(String id) {
  107.         if (id != null) {
  108.             requireNotNullOrEmpty(id, "id must either be null or not the empty String");
  109.         }
  110.         this.id = id;
  111.     }

  112.     /**
  113.      *
  114.      * @param packetID
  115.      * @deprecated use {@link #setStanzaId(String)} instead.
  116.      */
  117.     @Deprecated
  118.     public void setPacketID(String packetID) {
  119.         setStanzaId(packetID);
  120.     }

  121.     /**
  122.      * Check if this stanza has an ID set.
  123.      *
  124.      * @return true if the stanza ID is set, false otherwise.
  125.      * @since 4.1
  126.      */
  127.     public boolean hasStanzaIdSet() {
  128.         // setStanzaId ensures that the id is either null or not empty,
  129.         // so we can assume that it is set if it's not null.
  130.         return id != null;
  131.     }

  132.     /**
  133.      * Returns who the packet is being sent "to", or <tt>null</tt> if
  134.      * the value is not set. The XMPP protocol often makes the "to"
  135.      * attribute optional, so it does not always need to be set.<p>
  136.      *
  137.      * @return who the packet is being sent to, or <tt>null</tt> if the
  138.      *      value has not been set.
  139.      */
  140.     public Jid getTo() {
  141.         return to;
  142.     }

  143.     /**
  144.      * Sets who the packet is being sent "to". The XMPP protocol often makes
  145.      * the "to" attribute optional, so it does not always need to be set.
  146.      *
  147.      * @param to who the packet is being sent to.
  148.      * @throws IllegalArgumentException if to is not a valid JID String.
  149.      * @deprecated use {@link #setTo(Jid)} instead.
  150.      */
  151.     @Deprecated
  152.     public void setTo(String to) {
  153.         Jid jid;
  154.         try {
  155.             jid = JidCreate.from(to);
  156.         }
  157.         catch (XmppStringprepException e) {
  158.             throw new IllegalArgumentException(e);
  159.         }
  160.         setTo(jid);
  161.     }

  162.     /**
  163.      * Sets who the packet is being sent "to". The XMPP protocol often makes
  164.      * the "to" attribute optional, so it does not always need to be set.
  165.      *
  166.      * @param to who the packet is being sent to.
  167.      */
  168.     public void setTo(Jid to) {
  169.         this.to = to;
  170.     }

  171.     /**
  172.      * Returns who the packet is being sent "from" or <tt>null</tt> if
  173.      * the value is not set. The XMPP protocol often makes the "from"
  174.      * attribute optional, so it does not always need to be set.<p>
  175.      *
  176.      * @return who the packet is being sent from, or <tt>null</tt> if the
  177.      *      value has not been set.
  178.      */
  179.     public Jid getFrom() {
  180.         return from;
  181.     }

  182.     /**
  183.      * Sets who the packet is being sent "from". The XMPP protocol often
  184.      * makes the "from" attribute optional, so it does not always need to
  185.      * be set.
  186.      *
  187.      * @param from who the packet is being sent to.
  188.      * @throws IllegalArgumentException if from is not a valid JID String.
  189.      * @deprecated use {@link #setFrom(Jid)} instead.
  190.      */
  191.     @Deprecated
  192.     public void setFrom(String from) {
  193.         Jid jid;
  194.         try {
  195.             jid = JidCreate.from(from);
  196.         }
  197.         catch (XmppStringprepException e) {
  198.             throw new IllegalArgumentException(e);
  199.         }
  200.         setFrom(jid);
  201.     }

  202.     /**
  203.      * Sets who the packet is being sent "from". The XMPP protocol often
  204.      * makes the "from" attribute optional, so it does not always need to
  205.      * be set.
  206.      *
  207.      * @param from who the packet is being sent to.
  208.      */
  209.     public void setFrom(Jid from) {
  210.         this.from = from;
  211.     }

  212.     /**
  213.      * Returns the error associated with this packet, or <tt>null</tt> if there are
  214.      * no errors.
  215.      *
  216.      * @return the error sub-packet or <tt>null</tt> if there isn't an error.
  217.      */
  218.     public XMPPError getError() {
  219.         return error;
  220.     }

  221.     /**
  222.      * Sets the error for this packet.
  223.      *
  224.      * @param error the error to associate with this packet.
  225.      */
  226.     public void setError(XMPPError error) {
  227.         this.error = error;
  228.     }

  229.     /**
  230.      * Returns the xml:lang of this Stanza, or null if one has not been set.
  231.      *
  232.      * @return the xml:lang of this Stanza, or null.
  233.      */
  234.     public String getLanguage() {
  235.         return language;
  236.     }

  237.     /**
  238.      * Sets the xml:lang of this Stanza.
  239.      *
  240.      * @param language the xml:lang of this Stanza.
  241.      */
  242.     public void setLanguage(String language) {
  243.         this.language = language;
  244.     }

  245.     /**
  246.      * Returns a list of all extension elements of this stanza.
  247.      *
  248.      * @return a list of all extension elements of this stanza.
  249.      */
  250.     public List<ExtensionElement> getExtensions() {
  251.         synchronized (packetExtensions) {
  252.             // No need to create a new list, values() will already create a new one for us
  253.             return packetExtensions.values();
  254.         }
  255.     }

  256.     /**
  257.      * Return a set of all extensions with the given element name <emph>and</emph> namespace.
  258.      * <p>
  259.      * Changes to the returned set will update the packet extensions, if the returned set is not the empty set.
  260.      * </p>
  261.      *
  262.      * @param elementName the element name, must not be null.
  263.      * @param namespace the namespace of the element(s), must not be null.
  264.      * @return a set of all matching extensions.
  265.      * @since 4.1
  266.      */
  267.     public Set<ExtensionElement> getExtensions(String elementName, String namespace) {
  268.         requireNotNullOrEmpty(elementName, "elementName must not be null or empty");
  269.         requireNotNullOrEmpty(namespace, "namespace must not be null or empty");
  270.         String key = XmppStringUtils.generateKey(elementName, namespace);
  271.         return packetExtensions.getAll(key);
  272.     }

  273.     /**
  274.      * Returns the first extension of this packet that has the given namespace.
  275.      * <p>
  276.      * When possible, use {@link #getExtension(String,String)} instead.
  277.      * </p>
  278.      *
  279.      * @param namespace the namespace of the extension that is desired.
  280.      * @return the packet extension with the given namespace.
  281.      */
  282.     public ExtensionElement getExtension(String namespace) {
  283.         return PacketUtil.extensionElementFrom(getExtensions(), null, namespace);
  284.     }

  285.     /**
  286.      * Returns the first extension that matches the specified element name and
  287.      * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null,
  288.      * only the namespace is matched. Extensions are
  289.      * are arbitrary XML sub-documents in standard XMPP packets. By default, a
  290.      * {@link DefaultExtensionElement} instance will be returned for each extension. However,
  291.      * ExtensionElementProvider instances can be registered with the
  292.      * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
  293.      * class to handle custom parsing. In that case, the type of the Object
  294.      * will be determined by the provider.
  295.      *
  296.      * @param elementName the XML element name of the extension. (May be null)
  297.      * @param namespace the XML element namespace of the extension.
  298.      * @return the extension, or <tt>null</tt> if it doesn't exist.
  299.      */
  300.     @SuppressWarnings("unchecked")
  301.     public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) {
  302.         if (namespace == null) {
  303.             return null;
  304.         }
  305.         String key = XmppStringUtils.generateKey(elementName, namespace);
  306.         ExtensionElement packetExtension;
  307.         synchronized (packetExtensions) {
  308.             packetExtension = packetExtensions.getFirst(key);
  309.         }
  310.         if (packetExtension == null) {
  311.             return null;
  312.         }
  313.         return (PE) packetExtension;
  314.     }

  315.     /**
  316.      * Adds a packet extension to the packet. Does nothing if extension is null.
  317.      *
  318.      * @param extension a packet extension.
  319.      */
  320.     public void addExtension(ExtensionElement extension) {
  321.         if (extension == null) return;
  322.         String key = XmppStringUtils.generateKey(extension.getElementName(), extension.getNamespace());
  323.         synchronized (packetExtensions) {
  324.             packetExtensions.put(key, extension);
  325.         }
  326.     }

  327.     /**
  328.      * Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
  329.      *
  330.      * @param extensions a collection of packet extensions
  331.      */
  332.     public void addExtensions(Collection<ExtensionElement> extensions) {
  333.         if (extensions == null) return;
  334.         for (ExtensionElement packetExtension : extensions) {
  335.             addExtension(packetExtension);
  336.         }
  337.     }

  338.     /**
  339.      * Check if a packet extension with the given element and namespace exists.
  340.      * <p>
  341.      * The argument <code>elementName</code> may be null.
  342.      * </p>
  343.      *
  344.      * @param elementName
  345.      * @param namespace
  346.      * @return true if a packet extension exists, false otherwise.
  347.      */
  348.     public boolean hasExtension(String elementName, String namespace) {
  349.         if (elementName == null) {
  350.             return hasExtension(namespace);
  351.         }
  352.         String key = XmppStringUtils.generateKey(elementName, namespace);
  353.         synchronized (packetExtensions) {
  354.             return packetExtensions.containsKey(key);
  355.         }
  356.     }

  357.     /**
  358.      * Check if a packet extension with the given namespace exists.
  359.      *
  360.      * @param namespace
  361.      * @return true if a packet extension exists, false otherwise.
  362.      */
  363.     public boolean hasExtension(String namespace) {
  364.         synchronized (packetExtensions) {
  365.             for (ExtensionElement packetExtension : packetExtensions.values()) {
  366.                 if (packetExtension.getNamespace().equals(namespace)) {
  367.                     return true;
  368.                 }
  369.             }
  370.         }
  371.         return false;
  372.     }

  373.     /**
  374.      * Remove the packet extension with the given elementName and namespace.
  375.      *
  376.      * @param elementName
  377.      * @param namespace
  378.      * @return the removed packet extension or null.
  379.      */
  380.     public ExtensionElement removeExtension(String elementName, String namespace) {
  381.         String key = XmppStringUtils.generateKey(elementName, namespace);
  382.         synchronized (packetExtensions) {
  383.             return packetExtensions.remove(key);
  384.         }
  385.     }

  386.     /**
  387.      * Removes a packet extension from the packet.
  388.      *
  389.      * @param extension the packet extension to remove.
  390.      * @return the removed packet extension or null.
  391.      */
  392.     public ExtensionElement removeExtension(ExtensionElement extension)  {
  393.         return removeExtension(extension.getElementName(), extension.getNamespace());
  394.     }

  395.     @Override
  396.     // NOTE When Smack is using Java 8, then this method should be moved in Element as "Default Method".
  397.     public String toString() {
  398.         return toXML().toString();
  399.     }

  400.     /**
  401.      * Returns the extension sub-packets (including properties data) as an XML
  402.      * String, or the Empty String if there are no packet extensions.
  403.      *
  404.      * @return the extension sub-packets as XML or the Empty String if there
  405.      * are no packet extensions.
  406.      */
  407.     protected final XmlStringBuilder getExtensionsXML() {
  408.         XmlStringBuilder xml = new XmlStringBuilder();
  409.         // Add in all standard extension sub-packets.
  410.         for (ExtensionElement extension : getExtensions()) {
  411.             xml.append(extension.toXML());
  412.         }
  413.         return xml;
  414.     }

  415.     /**
  416.      * Returns the default language used for all messages containing localized content.
  417.      *
  418.      * @return the default language
  419.      */
  420.     public static String getDefaultLanguage() {
  421.         return DEFAULT_LANGUAGE;
  422.     }

  423.     /**
  424.      * Add to, from, id and 'xml:lang' attributes
  425.      *
  426.      * @param xml
  427.      */
  428.     protected void addCommonAttributes(XmlStringBuilder xml) {
  429.         xml.optAttribute("to", getTo());
  430.         xml.optAttribute("from", getFrom());
  431.         xml.optAttribute("id", getStanzaId());
  432.         xml.xmllangAttribute(getLanguage());
  433.     }

  434.     /**
  435.      * Append an XMPPError is this packet has one set.
  436.      *
  437.      * @param xml the XmlStringBuilder to append the error to.
  438.      */
  439.     protected void appendErrorIfExists(XmlStringBuilder xml) {
  440.         XMPPError error = getError();
  441.         if (error != null) {
  442.             xml.append(error.toXML());
  443.         }
  444.     }
  445. }