001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.packet; 019 020import static org.jivesoftware.smack.util.StringUtils.requireNotNullOrEmpty; 021 022import org.jivesoftware.smack.packet.id.StanzaIdUtil; 023import org.jivesoftware.smack.util.MultiMap; 024import org.jivesoftware.smack.util.PacketUtil; 025import org.jivesoftware.smack.util.XmlStringBuilder; 026import org.jxmpp.util.XmppStringUtils; 027 028import java.util.Collection; 029import java.util.List; 030import java.util.Locale; 031import java.util.Set; 032 033/** 034 * Base class for XMPP Stanzas, which are called Stanza(/Packet) in older versions of Smack (i.e. < 4.1). 035 * <p> 036 * Every stanza has a unique ID (which is automatically generated, but can be overridden). Stanza 037 * IDs are required for IQ stanzas and recommended for presence and message stanzas. Optionally, the 038 * "to" and "from" fields can be set. 039 * </p> 040 * <p> 041 * XMPP Stanzas are {@link Message}, {@link IQ} and {@link Presence}. Which therefore subclass this 042 * class. <b>If you think you need to subclass this class, then you are doing something wrong.</b> 043 * </p> 044 * 045 * @author Matt Tucker 046 * @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas">RFC 6120 ยง 8. XML Stanzas</a> 047 */ 048@SuppressWarnings("deprecation") // FIXME Remove when 'Packet' is removed from Smack 049public abstract class Stanza implements TopLevelStreamElement, Packet { 050 051 public static final String TEXT = "text"; 052 public static final String ITEM = "item"; 053 054 protected static final String DEFAULT_LANGUAGE = 055 java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US); 056 057 private final MultiMap<String, ExtensionElement> packetExtensions = new MultiMap<>(); 058 059 private String id = null; 060 private String to = null; 061 private String from = null; 062 private XMPPError error = null; 063 064 /** 065 * Optional value of the 'xml:lang' attribute of the outermost element of 066 * the stanza. 067 * <p> 068 * Such an attribute is defined for all stanza types. For IQ, see for 069 * example XEP-50 3.7: 070 * "The requester SHOULD provide its locale information using the "xml:lang 071 * " attribute on either the <iq/> (RECOMMENDED) or <command/> element." 072 * </p> 073 */ 074 protected String language; 075 076 protected Stanza() { 077 this(StanzaIdUtil.newStanzaId()); 078 } 079 080 protected Stanza(String stanzaId) { 081 setStanzaId(stanzaId); 082 } 083 084 protected Stanza(Stanza p) { 085 id = p.getStanzaId(); 086 to = p.getTo(); 087 from = p.getFrom(); 088 error = p.error; 089 090 // Copy extensions 091 for (ExtensionElement pe : p.getExtensions()) { 092 addExtension(pe); 093 } 094 } 095 096 /** 097 * Returns the unique ID of the stanza. The returned value could be <code>null</code>. 098 * 099 * @return the packet's unique ID or <code>null</code> if the id is not available. 100 */ 101 public String getStanzaId() { 102 return id; 103 } 104 105 /** 106 * 107 * @return the stanza id. 108 * @deprecated use {@link #getStanzaId()} instead. 109 */ 110 @Deprecated 111 public String getPacketID() { 112 return getStanzaId(); 113 } 114 115 /** 116 * Sets the unique ID of the packet. To indicate that a stanza(/packet) has no id 117 * pass <code>null</code> as the packet's id value. 118 * 119 * @param id the unique ID for the packet. 120 */ 121 public void setStanzaId(String id) { 122 if (id != null) { 123 requireNotNullOrEmpty(id, "id must either be null or not the empty String"); 124 } 125 this.id = id; 126 } 127 128 /** 129 * 130 * @param packetID 131 * @deprecated use {@link #setStanzaId(String)} instead. 132 */ 133 @Deprecated 134 public void setPacketID(String packetID) { 135 setStanzaId(packetID); 136 } 137 138 /** 139 * Check if this stanza has an ID set. 140 * 141 * @return true if the stanza ID is set, false otherwise. 142 * @since 4.1 143 */ 144 public boolean hasStanzaIdSet() { 145 // setStanzaId ensures that the id is either null or not empty, 146 // so we can assume that it is set if it's not null. 147 return id != null; 148 } 149 150 /** 151 * Returns who the stanza(/packet) is being sent "to", or <tt>null</tt> if 152 * the value is not set. The XMPP protocol often makes the "to" 153 * attribute optional, so it does not always need to be set.<p> 154 * 155 * @return who the stanza(/packet) is being sent to, or <tt>null</tt> if the 156 * value has not been set. 157 */ 158 public String getTo() { 159 return to; 160 } 161 162 /** 163 * Sets who the stanza(/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 stanza(/packet) is being sent to. 167 */ 168 public void setTo(String to) { 169 this.to = to; 170 } 171 172 /** 173 * Returns who the stanza(/packet) is being sent "from" or <tt>null</tt> if 174 * the value is not set. The XMPP protocol often makes the "from" 175 * attribute optional, so it does not always need to be set.<p> 176 * 177 * @return who the stanza(/packet) is being sent from, or <tt>null</tt> if the 178 * value has not been set. 179 */ 180 public String getFrom() { 181 return from; 182 } 183 184 /** 185 * Sets who the stanza(/packet) is being sent "from". The XMPP protocol often 186 * makes the "from" attribute optional, so it does not always need to 187 * be set. 188 * 189 * @param from who the stanza(/packet) is being sent to. 190 */ 191 public void setFrom(String from) { 192 this.from = from; 193 } 194 195 /** 196 * Returns the error associated with this packet, or <tt>null</tt> if there are 197 * no errors. 198 * 199 * @return the error sub-packet or <tt>null</tt> if there isn't an error. 200 */ 201 public XMPPError getError() { 202 return error; 203 } 204 205 /** 206 * Sets the error for this packet. 207 * 208 * @param error the error to associate with this packet. 209 */ 210 public void setError(XMPPError error) { 211 this.error = error; 212 } 213 214 /** 215 * Returns the xml:lang of this Stanza, or null if one has not been set. 216 * 217 * @return the xml:lang of this Stanza, or null. 218 */ 219 public String getLanguage() { 220 return language; 221 } 222 223 /** 224 * Sets the xml:lang of this Stanza. 225 * 226 * @param language the xml:lang of this Stanza. 227 */ 228 public void setLanguage(String language) { 229 this.language = language; 230 } 231 232 /** 233 * Returns a list of all extension elements of this stanza. 234 * 235 * @return a list of all extension elements of this stanza. 236 */ 237 public List<ExtensionElement> getExtensions() { 238 synchronized (packetExtensions) { 239 // No need to create a new list, values() will already create a new one for us 240 return packetExtensions.values(); 241 } 242 } 243 244 /** 245 * Return a set of all extensions with the given element name <emph>and</emph> namespace. 246 * <p> 247 * Changes to the returned set will update the stanza(/packet) extensions, if the returned set is not the empty set. 248 * </p> 249 * 250 * @param elementName the element name, must not be null. 251 * @param namespace the namespace of the element(s), must not be null. 252 * @return a set of all matching extensions. 253 * @since 4.1 254 */ 255 public Set<ExtensionElement> getExtensions(String elementName, String namespace) { 256 requireNotNullOrEmpty(elementName, "elementName must not be null or empty"); 257 requireNotNullOrEmpty(namespace, "namespace must not be null or empty"); 258 String key = XmppStringUtils.generateKey(elementName, namespace); 259 return packetExtensions.getAll(key); 260 } 261 262 /** 263 * Returns the first extension of this stanza(/packet) that has the given namespace. 264 * <p> 265 * When possible, use {@link #getExtension(String,String)} instead. 266 * </p> 267 * 268 * @param namespace the namespace of the extension that is desired. 269 * @return the stanza(/packet) extension with the given namespace. 270 */ 271 public ExtensionElement getExtension(String namespace) { 272 return PacketUtil.extensionElementFrom(getExtensions(), null, namespace); 273 } 274 275 /** 276 * Returns the first extension that matches the specified element name and 277 * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null, 278 * only the namespace is matched. Extensions are 279 * are arbitrary XML sub-documents in standard XMPP packets. By default, a 280 * {@link DefaultExtensionElement} instance will be returned for each extension. However, 281 * ExtensionElementProvider instances can be registered with the 282 * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager} 283 * class to handle custom parsing. In that case, the type of the Object 284 * will be determined by the provider. 285 * 286 * @param elementName the XML element name of the extension. (May be null) 287 * @param namespace the XML element namespace of the extension. 288 * @return the extension, or <tt>null</tt> if it doesn't exist. 289 */ 290 @SuppressWarnings("unchecked") 291 public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) { 292 if (namespace == null) { 293 return null; 294 } 295 String key = XmppStringUtils.generateKey(elementName, namespace); 296 ExtensionElement packetExtension; 297 synchronized (packetExtensions) { 298 packetExtension = packetExtensions.getFirst(key); 299 } 300 if (packetExtension == null) { 301 return null; 302 } 303 return (PE) packetExtension; 304 } 305 306 /** 307 * Adds a stanza(/packet) extension to the packet. Does nothing if extension is null. 308 * 309 * @param extension a stanza(/packet) extension. 310 */ 311 public void addExtension(ExtensionElement extension) { 312 if (extension == null) return; 313 String key = XmppStringUtils.generateKey(extension.getElementName(), extension.getNamespace()); 314 synchronized (packetExtensions) { 315 packetExtensions.put(key, extension); 316 } 317 } 318 319 /** 320 * Add the given extension and override eventually existing extensions with the same name and 321 * namespace. 322 * 323 * @param extension the extension element to add. 324 * @return one of the removed extensions or <code>null</code> if there are none. 325 * @since 4.1.2 326 */ 327 public ExtensionElement overrideExtension(ExtensionElement extension) { 328 if (extension == null) return null; 329 synchronized (packetExtensions) { 330 ExtensionElement removedExtension = removeExtension(extension); 331 addExtension(extension); 332 return removedExtension; 333 } 334 } 335 336 /** 337 * Adds a collection of stanza(/packet) extensions to the packet. Does nothing if extensions is null. 338 * 339 * @param extensions a collection of stanza(/packet) extensions 340 */ 341 public void addExtensions(Collection<ExtensionElement> extensions) { 342 if (extensions == null) return; 343 for (ExtensionElement packetExtension : extensions) { 344 addExtension(packetExtension); 345 } 346 } 347 348 /** 349 * Check if a stanza(/packet) extension with the given element and namespace exists. 350 * <p> 351 * The argument <code>elementName</code> may be null. 352 * </p> 353 * 354 * @param elementName 355 * @param namespace 356 * @return true if a stanza(/packet) extension exists, false otherwise. 357 */ 358 public boolean hasExtension(String elementName, String namespace) { 359 if (elementName == null) { 360 return hasExtension(namespace); 361 } 362 String key = XmppStringUtils.generateKey(elementName, namespace); 363 synchronized (packetExtensions) { 364 return packetExtensions.containsKey(key); 365 } 366 } 367 368 /** 369 * Check if a stanza(/packet) extension with the given namespace exists. 370 * 371 * @param namespace 372 * @return true if a stanza(/packet) extension exists, false otherwise. 373 */ 374 public boolean hasExtension(String namespace) { 375 synchronized (packetExtensions) { 376 for (ExtensionElement packetExtension : packetExtensions.values()) { 377 if (packetExtension.getNamespace().equals(namespace)) { 378 return true; 379 } 380 } 381 } 382 return false; 383 } 384 385 /** 386 * Remove the stanza(/packet) extension with the given elementName and namespace. 387 * 388 * @param elementName 389 * @param namespace 390 * @return the removed stanza(/packet) extension or null. 391 */ 392 public ExtensionElement removeExtension(String elementName, String namespace) { 393 String key = XmppStringUtils.generateKey(elementName, namespace); 394 synchronized (packetExtensions) { 395 return packetExtensions.remove(key); 396 } 397 } 398 399 /** 400 * Removes a stanza(/packet) extension from the packet. 401 * 402 * @param extension the stanza(/packet) extension to remove. 403 * @return the removed stanza(/packet) extension or null. 404 */ 405 public ExtensionElement removeExtension(ExtensionElement extension) { 406 return removeExtension(extension.getElementName(), extension.getNamespace()); 407 } 408 409 @Override 410 // NOTE When Smack is using Java 8, then this method should be moved in Element as "Default Method". 411 public String toString() { 412 return toXML().toString(); 413 } 414 415 /** 416 * Returns the extension sub-packets (including properties data) as an XML 417 * String, or the Empty String if there are no stanza(/packet) extensions. 418 * 419 * @return the extension sub-packets as XML or the Empty String if there 420 * are no stanza(/packet) extensions. 421 */ 422 protected final XmlStringBuilder getExtensionsXML() { 423 XmlStringBuilder xml = new XmlStringBuilder(); 424 // Add in all standard extension sub-packets. 425 for (ExtensionElement extension : getExtensions()) { 426 xml.append(extension.toXML()); 427 } 428 return xml; 429 } 430 431 /** 432 * Returns the default language used for all messages containing localized content. 433 * 434 * @return the default language 435 */ 436 public static String getDefaultLanguage() { 437 return DEFAULT_LANGUAGE; 438 } 439 440 /** 441 * Add to, from, id and 'xml:lang' attributes 442 * 443 * @param xml 444 */ 445 protected void addCommonAttributes(XmlStringBuilder xml) { 446 xml.optAttribute("to", getTo()); 447 xml.optAttribute("from", getFrom()); 448 xml.optAttribute("id", getStanzaId()); 449 xml.xmllangAttribute(getLanguage()); 450 } 451 452 /** 453 * Append an XMPPError is this stanza(/packet) has one set. 454 * 455 * @param xml the XmlStringBuilder to append the error to. 456 */ 457 protected void appendErrorIfExists(XmlStringBuilder xml) { 458 XMPPError error = getError(); 459 if (error != null) { 460 xml.append(error.toXML()); 461 } 462 } 463}