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.requireNotNullNorEmpty; 021 022import java.util.Collection; 023import java.util.Collections; 024import java.util.List; 025import java.util.Locale; 026 027import javax.xml.namespace.QName; 028 029import org.jivesoftware.smack.packet.id.StandardStanzaIdSource; 030import org.jivesoftware.smack.packet.id.StanzaIdSource; 031import org.jivesoftware.smack.util.MultiMap; 032import org.jivesoftware.smack.util.PacketUtil; 033import org.jivesoftware.smack.util.StringUtils; 034import org.jivesoftware.smack.util.XmlStringBuilder; 035import org.jivesoftware.smack.util.XmppElementUtil; 036 037import org.jxmpp.jid.Jid; 038 039/** 040 * Base class for XMPP Stanzas, which are called Stanza in older versions of Smack (i.e. < 4.1). 041 * <p> 042 * Every stanza has a unique ID (which is automatically generated, but can be overridden). Stanza 043 * IDs are required for IQ stanzas and recommended for presence and message stanzas. Optionally, the 044 * "to" and "from" fields can be set. 045 * </p> 046 * <p> 047 * XMPP Stanzas are {@link Message}, {@link IQ} and {@link Presence}. Which therefore subclass this 048 * class. <b>If you think you need to subclass this class, then you are doing something wrong.</b> 049 * </p> 050 * <p> 051 * Use {@link StanzaBuilder} to construct a stanza instance. All instance mutating methods of this 052 * class are deprecated, although not all of them are currently marked as such, and must not be used. 053 * </p> 054 * 055 * @author Matt Tucker 056 * @author Florian Schmaus 057 * @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas">RFC 6120 ยง 8. XML Stanzas</a> 058 */ 059public abstract class Stanza implements StanzaView, TopLevelStreamElement { 060 061 public static final String TEXT = "text"; 062 public static final String ITEM = "item"; 063 064 protected static final String DEFAULT_LANGUAGE = 065 java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US); 066 067 private final MultiMap<QName, ExtensionElement> extensionElements; 068 069 // Assume that all stanzas Smack handles are in the client namespace, since Smack is an XMPP client library. We can 070 // change this behavior later if it is required. 071 private final String namespace = StreamOpen.CLIENT_NAMESPACE; 072 073 private final StanzaIdSource usedStanzaIdSource; 074 075 private String id = null; 076 private Jid to; 077 private Jid from; 078 private StanzaError error = null; 079 080 /** 081 * Optional value of the 'xml:lang' attribute of the outermost element of 082 * the stanza. 083 * <p> 084 * Such an attribute is defined for all stanza types. For IQ, see for 085 * example XEP-50 3.7: 086 * "The requester SHOULD provide its locale information using the "xml:lang 087 * " attribute on either the <iq/> (RECOMMENDED) or <command/> element." 088 * </p> 089 */ 090 protected String language; 091 092 protected Stanza() { 093 extensionElements = new MultiMap<>(); 094 usedStanzaIdSource = null; 095 id = StandardStanzaIdSource.DEFAULT.getNewStanzaId(); 096 } 097 098 protected Stanza(StanzaBuilder<?> stanzaBuilder) { 099 if (stanzaBuilder.stanzaIdSource != null) { 100 id = stanzaBuilder.stanzaIdSource.getNewStanzaId(); 101 // Note that some stanza ID sources, e.g. StanzaBuilder.PresenceBuilder.EMPTY return null here. Hence we 102 // only check that the returned string is not empty. 103 assert StringUtils.isNullOrNotEmpty(id); 104 usedStanzaIdSource = stanzaBuilder.stanzaIdSource; 105 } else { 106 // N.B. It is ok if stanzaId here is null. 107 id = stanzaBuilder.stanzaId; 108 usedStanzaIdSource = null; 109 } 110 111 to = stanzaBuilder.to; 112 from = stanzaBuilder.from; 113 114 error = stanzaBuilder.stanzaError; 115 116 language = stanzaBuilder.language; 117 118 extensionElements = stanzaBuilder.extensionElements.clone(); 119 } 120 121 protected Stanza(Stanza p) { 122 usedStanzaIdSource = p.usedStanzaIdSource; 123 124 id = p.getStanzaId(); 125 to = p.getTo(); 126 from = p.getFrom(); 127 error = p.error; 128 129 extensionElements = p.extensionElements.clone(); 130 } 131 132 @Override 133 public final String getStanzaId() { 134 return id; 135 } 136 137 /** 138 * Sets the unique ID of the packet. To indicate that a stanza has no id 139 * pass <code>null</code> as the packet's id value. 140 * 141 * @param id the unique ID for the packet. 142 */ 143 public void setStanzaId(String id) { 144 if (id != null) { 145 requireNotNullNorEmpty(id, "id must either be null or not the empty String"); 146 } 147 this.id = id; 148 } 149 150 /** 151 * Check if this stanza has an ID set. 152 * 153 * @return true if the stanza ID is set, false otherwise. 154 * @since 4.1 155 */ 156 public final boolean hasStanzaIdSet() { 157 // setStanzaId ensures that the id is either null or not empty, 158 // so we can assume that it is set if it's not null. 159 return id != null; 160 } 161 162 /** 163 * Set the stanza id if none is set. 164 * 165 * @return the stanza id. 166 * @since 4.2 167 * @deprecated use {@link StanzaBuilder} instead. 168 */ 169 @Deprecated 170 // TODO: Remove in Smack 4.5. 171 public String setStanzaId() { 172 if (!hasStanzaIdSet()) { 173 setNewStanzaId(); 174 } 175 return getStanzaId(); 176 } 177 178 /** 179 * Throws an {@link IllegalArgumentException} if this stanza has no stanza ID set. 180 * 181 * @throws IllegalArgumentException if this stanza has no stanza ID set. 182 * @since 4.4. 183 */ 184 public final void throwIfNoStanzaId() { 185 if (hasStanzaIdSet()) { 186 return; 187 } 188 189 throw new IllegalArgumentException("The stanza has no RFC stanza ID set, although one is required"); 190 } 191 192 /** 193 * Ensure that a stanza ID is set. 194 * 195 * @return the stanza ID. 196 * @since 4.4 197 */ 198 // TODO: Remove this method once StanzaBuilder is ready. 199 protected String setNewStanzaId() { 200 if (usedStanzaIdSource != null) { 201 id = usedStanzaIdSource.getNewStanzaId(); 202 } 203 else { 204 id = StandardStanzaIdSource.DEFAULT.getNewStanzaId(); 205 } 206 207 return getStanzaId(); 208 } 209 210 @Override 211 public final Jid getTo() { 212 return to; 213 } 214 215 /** 216 * Sets who the packet is being sent "to". The XMPP protocol often makes 217 * the "to" attribute optional, so it does not always need to be set. 218 * 219 * @param to who the packet is being sent to. 220 */ 221 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 222 public void setTo(Jid to) { 223 this.to = to; 224 } 225 226 @Override 227 public final Jid getFrom() { 228 return from; 229 } 230 231 /** 232 * Sets who the packet is being sent "from". The XMPP protocol often 233 * makes the "from" attribute optional, so it does not always need to 234 * be set. 235 * 236 * @param from who the packet is being sent to. 237 */ 238 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 239 public void setFrom(Jid from) { 240 this.from = from; 241 } 242 243 @Override 244 public final StanzaError getError() { 245 return error; 246 } 247 248 /** 249 * Sets the error for this stanza. 250 * 251 * @param stanzaError the error that this stanza carries and hence signals. 252 */ 253 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 254 public void setError(StanzaError stanzaError) { 255 error = stanzaError; 256 } 257 258 /** 259 * Deprecated. 260 * @param stanzaError the stanza error. 261 * @deprecated use {@link StanzaBuilder} instead. 262 */ 263 @Deprecated 264 // TODO: Remove in Smack 4.5. 265 public void setError(StanzaError.Builder stanzaError) { 266 setError(stanzaError.build()); 267 } 268 269 @Override 270 public final String getLanguage() { 271 return language; 272 } 273 274 /** 275 * Sets the xml:lang of this Stanza. 276 * 277 * @param language the xml:lang of this Stanza. 278 * @deprecated use {@link StanzaBuilder#setLanguage(String)} instead. 279 */ 280 @Deprecated 281 // TODO: Remove in Smack 4.5. 282 public void setLanguage(String language) { 283 this.language = language; 284 } 285 286 @Override 287 public final List<ExtensionElement> getExtensions() { 288 synchronized (extensionElements) { 289 // No need to create a new list, values() will already create a new one for us 290 return extensionElements.values(); 291 } 292 } 293 294 public final MultiMap<QName, ExtensionElement> getExtensionsMap() { 295 return cloneExtensionsMap(); 296 } 297 298 final MultiMap<QName, ExtensionElement> cloneExtensionsMap() { 299 synchronized (extensionElements) { 300 return extensionElements.clone(); 301 } 302 } 303 304 /** 305 * Return a list of all extensions with the given element name <em>and</em> namespace. 306 * <p> 307 * Changes to the returned set will update the stanza extensions, if the returned set is not the empty set. 308 * </p> 309 * 310 * @param elementName the element name, must not be null. 311 * @param namespace the namespace of the element(s), must not be null. 312 * @return a set of all matching extensions. 313 * @since 4.1 314 */ 315 public final List<ExtensionElement> getExtensions(String elementName, String namespace) { 316 requireNotNullNorEmpty(elementName, "elementName must not be null nor empty"); 317 requireNotNullNorEmpty(namespace, "namespace must not be null nor empty"); 318 QName key = new QName(namespace, elementName); 319 return getExtensions(key); 320 } 321 322 @Override 323 public final List<ExtensionElement> getExtensions(QName qname) { 324 List<ExtensionElement> res; 325 synchronized (extensionElements) { 326 res = extensionElements.getAll(qname); 327 } 328 return Collections.unmodifiableList(res); 329 } 330 331 @Override 332 public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) { 333 synchronized (extensionElements) { 334 return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass); 335 } 336 } 337 338 /** 339 * Returns the first extension of this stanza that has the given namespace. 340 * <p> 341 * When possible, use {@link #getExtensionElement(String, String)} instead. 342 * </p> 343 * 344 * @param namespace the namespace of the extension that is desired. 345 * @return the stanza extension with the given namespace. 346 */ 347 public final ExtensionElement getExtension(String namespace) { 348 return PacketUtil.extensionElementFrom(getExtensions(), null, namespace); 349 } 350 351 /** 352 * Returns the first extension that matches the specified element name and 353 * namespace, or <code>null</code> if it doesn't exist. Extensions are 354 * are arbitrary XML elements in standard XMPP stanzas. 355 * <p> 356 * Note that it is recommended to use {@link #getExtension(Class)} instead of this method. 357 * The {@link #getExtension(Class)} is more robust, as it is type safe. 358 * </p> 359 * 360 * @param elementName the XML element name of the extension. 361 * @param namespace the XML element namespace of the extension. 362 * @return the extension, or <code>null</code> if it doesn't exist. 363 */ 364 public final ExtensionElement getExtensionElement(String elementName, String namespace) { 365 if (namespace == null) { 366 return null; 367 } 368 QName key = new QName(namespace, elementName); 369 ExtensionElement packetExtension = getExtension(key); 370 if (packetExtension == null) { 371 return null; 372 } 373 return packetExtension; 374 } 375 376 /** 377 * This method is deprecated. Use preferably {@link #getExtension(Class)} or {@link #getExtensionElement(String, String)}. 378 * 379 * @param <E> the type to cast to. 380 * @param elementName the XML element name of the extension. (May be null) 381 * @param namespace the XML element namespace of the extension. 382 * @return the extension, or <code>null</code> if it doesn't exist. 383 * @deprecated use {@link #getExtension(Class)} or {@link #getExtensionElement(String, String)} instead. 384 */ 385 // TODO: Remove in Smack 4.5. 386 @SuppressWarnings("unchecked") 387 @Deprecated 388 public final <E extends ExtensionElement> E getExtension(String elementName, String namespace) { 389 return (E) getExtensionElement(elementName, namespace); 390 } 391 392 @Override 393 public final ExtensionElement getExtension(QName qname) { 394 synchronized (extensionElements) { 395 return extensionElements.getFirst(qname); 396 } 397 } 398 399 /** 400 * Adds a stanza extension to the packet. Does nothing if extension is null. 401 * <p> 402 * Please note that although this method is not yet marked as deprecated, it is recommended to use 403 * {@link StanzaBuilder#addExtension(ExtensionElement)} instead. 404 * </p> 405 * 406 * @param extension a stanza extension. 407 */ 408 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 409 public final void addExtension(ExtensionElement extension) { 410 if (extension == null) return; 411 QName key = extension.getQName(); 412 synchronized (extensionElements) { 413 extensionElements.put(key, extension); 414 } 415 } 416 417 /** 418 * Add the given extension and override eventually existing extensions with the same name and 419 * namespace. 420 * <p> 421 * Please note that although this method is not yet marked as deprecated, it is recommended to use 422 * {@link StanzaBuilder#overrideExtension(ExtensionElement)} instead. 423 * </p> 424 * 425 * @param extension the extension element to add. 426 * @return one of the removed extensions or <code>null</code> if there are none. 427 * @since 4.1.2 428 */ 429 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 430 public final ExtensionElement overrideExtension(ExtensionElement extension) { 431 if (extension == null) return null; 432 synchronized (extensionElements) { 433 // Note that we need to use removeExtension(String, String) here. If would use 434 // removeExtension(ExtensionElement) then we would remove based on the equality of ExtensionElement, which 435 // is not what we want in this case. 436 ExtensionElement removedExtension = removeExtension(extension.getElementName(), extension.getNamespace()); 437 addExtension(extension); 438 return removedExtension; 439 } 440 } 441 442 /** 443 * Adds a collection of stanza extensions to the packet. Does nothing if extensions is null. 444 * 445 * @param extensions a collection of stanza extensions 446 */ 447 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 448 public final void addExtensions(Collection<? extends ExtensionElement> extensions) { 449 if (extensions == null) return; 450 for (ExtensionElement packetExtension : extensions) { 451 addExtension(packetExtension); 452 } 453 } 454 455 /** 456 * Check if a stanza extension with the given element and namespace exists. 457 * <p> 458 * The argument <code>elementName</code> may be null. 459 * </p> 460 * 461 * @param elementName TODO javadoc me please 462 * @param namespace TODO javadoc me please 463 * @return true if a stanza extension exists, false otherwise. 464 */ 465 public final boolean hasExtension(String elementName, String namespace) { 466 if (elementName == null) { 467 return hasExtension(namespace); 468 } 469 QName key = new QName(namespace, elementName); 470 synchronized (extensionElements) { 471 return extensionElements.containsKey(key); 472 } 473 } 474 475 // Overridden in order to avoid an extra copy. 476 @Override 477 public final boolean hasExtension(String namespace) { 478 synchronized (extensionElements) { 479 for (ExtensionElement packetExtension : extensionElements.values()) { 480 if (packetExtension.getNamespace().equals(namespace)) { 481 return true; 482 } 483 } 484 } 485 return false; 486 } 487 488 /** 489 * Remove the stanza extension with the given elementName and namespace. 490 * 491 * @param elementName TODO javadoc me please 492 * @param namespace TODO javadoc me please 493 * @return the removed stanza extension or null. 494 */ 495 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 496 public final ExtensionElement removeExtension(String elementName, String namespace) { 497 QName key = new QName(namespace, elementName); 498 synchronized (extensionElements) { 499 return extensionElements.remove(key); 500 } 501 } 502 503 /** 504 * Removes a stanza extension from the packet. 505 * 506 * @param extension the stanza extension to remove. 507 * @return the removed stanza extension or null. 508 * @deprecated use {@link StanzaBuilder} instead. 509 */ 510 @Deprecated 511 // TODO: Remove in Smack 4.5. 512 public final ExtensionElement removeExtension(ExtensionElement extension) { 513 QName key = extension.getQName(); 514 synchronized (extensionElements) { 515 List<ExtensionElement> list = extensionElements.getAll(key); 516 boolean removed = list.remove(extension); 517 if (removed) { 518 return extension; 519 } 520 } 521 return null; 522 } 523 524 /** 525 * Returns a short String describing the Stanza. This method is suited for log purposes. 526 */ 527 @Override 528 public abstract String toString(); 529 530 @Override 531 public final String getNamespace() { 532 return namespace; 533 } 534 535 /** 536 * Returns the default language used for all messages containing localized content. 537 * 538 * @return the default language 539 */ 540 public static String getDefaultLanguage() { 541 return DEFAULT_LANGUAGE; 542 } 543 544 /** 545 * Add to, from, and id attributes. 546 * 547 * @param xml the {@link XmlStringBuilder}. 548 */ 549 protected final void addCommonAttributes(XmlStringBuilder xml) { 550 xml.optAttribute("to", getTo()); 551 xml.optAttribute("from", getFrom()); 552 xml.optAttribute("id", getStanzaId()); 553 } 554 555 protected void logCommonAttributes(StringBuilder sb) { 556 if (getTo() != null) { 557 sb.append("to=").append(to).append(','); 558 } 559 if (getFrom() != null) { 560 sb.append("from=").append(from).append(','); 561 } 562 if (hasStanzaIdSet()) { 563 sb.append("id=").append(id).append(','); 564 } 565 } 566 567 /** 568 * Append an XMPPError is this stanza has one set. 569 * 570 * @param xml the XmlStringBuilder to append the error to. 571 */ 572 protected final void appendErrorIfExists(XmlStringBuilder xml) { 573 StanzaError error = getError(); 574 if (error != null) { 575 xml.append(error); 576 } 577 } 578 579 /** 580 * Return the provided non-empty language, or use this {@link XmlLangElement} language (if set). 581 * 582 * @param language the provided language, may be the empty string or <code>null</code>. 583 * @return the provided language or this element's language (if set). 584 */ 585 static String determineLanguage(XmlLangElement xmlLangElement, String language) { 586 if (language != null && !language.isEmpty()) { 587 return language; 588 } 589 590 return xmlLangElement.getLanguage(); 591 } 592}