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, XmlElement> 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 &lt;iq/&gt; (RECOMMENDED) or &lt;command/&gt; 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<XmlElement> 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, XmlElement> getExtensionsMap() {
295        return cloneExtensionsMap();
296    }
297
298    final MultiMap<QName, XmlElement> 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<XmlElement> 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<XmlElement> getExtensions(QName qname) {
324        List<XmlElement> 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    // TODO: Mark this method as deprecated in favor of getExtension(QName).
348    public final XmlElement getExtension(String namespace) {
349        return PacketUtil.extensionElementFrom(getExtensions(), null, namespace);
350    }
351
352    /**
353     * Returns the first extension that matches the specified element name and
354     * namespace, or <code>null</code> if it doesn't exist. Extensions are
355     * are arbitrary XML elements in standard XMPP stanzas.
356     * <p>
357     * Note that it is recommended to use {@link #getExtension(Class)} instead of this method.
358     * The {@link #getExtension(Class)} is more robust, as it is type safe.
359     * </p>
360     *
361     * @param elementName the XML element name of the extension.
362     * @param namespace the XML element namespace of the extension.
363     * @return the extension, or <code>null</code> if it doesn't exist.
364     */
365    public final XmlElement getExtensionElement(String elementName, String namespace) {
366        if (namespace == null) {
367            return null;
368        }
369        QName key = new QName(namespace, elementName);
370        XmlElement packetExtension = getExtension(key);
371        if (packetExtension == null) {
372            return null;
373        }
374        return packetExtension;
375    }
376
377    /**
378     * This method is deprecated. Use preferably {@link #getExtension(Class)} or {@link #getExtensionElement(String, String)}.
379     *
380     * @param <E> the type to cast to.
381     * @param elementName the XML element name of the extension. (May be null)
382     * @param namespace the XML element namespace of the extension.
383     * @return the extension, or <code>null</code> if it doesn't exist.
384     * @deprecated use {@link #getExtension(Class)} or {@link #getExtensionElement(String, String)} instead.
385     */
386    // TODO: Remove in Smack 4.5.
387    @SuppressWarnings("unchecked")
388    @Deprecated
389    public final <E extends ExtensionElement> E getExtension(String elementName, String namespace) {
390        return (E) getExtensionElement(elementName, namespace);
391    }
392
393    @Override
394    public final XmlElement getExtension(QName qname) {
395        synchronized (extensionElements) {
396            return extensionElements.getFirst(qname);
397        }
398    }
399
400    /**
401     * Adds a stanza extension to the packet. Does nothing if extension is null.
402     * <p>
403     * Please note that although this method is not yet marked as deprecated, it is recommended to use
404     * {@link StanzaBuilder#addExtension(XmlElement)} instead.
405     * </p>
406     *
407     * @param extension a stanza extension.
408     */
409    // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
410    public final void addExtension(XmlElement extension) {
411        if (extension == null) return;
412        QName key = extension.getQName();
413        synchronized (extensionElements) {
414            extensionElements.put(key, extension);
415        }
416    }
417
418    /**
419     * Add the given extension and override eventually existing extensions with the same name and
420     * namespace.
421     * <p>
422     * Please note that although this method is not yet marked as deprecated, it is recommended to use
423     * {@link StanzaBuilder#overrideExtension(XmlElement)} instead.
424     * </p>
425     *
426     * @param extension the extension element to add.
427     * @return one of the removed extensions or <code>null</code> if there are none.
428     * @since 4.1.2
429     */
430    // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
431    public final XmlElement overrideExtension(XmlElement extension) {
432        if (extension == null) return null;
433        synchronized (extensionElements) {
434            // Note that we need to use removeExtension(String, String) here. If would use
435            // removeExtension(ExtensionElement) then we would remove based on the equality of ExtensionElement, which
436            // is not what we want in this case.
437            XmlElement removedExtension = removeExtension(extension.getElementName(), extension.getNamespace());
438            addExtension(extension);
439            return removedExtension;
440        }
441    }
442
443    /**
444     * Adds a collection of stanza extensions to the packet. Does nothing if extensions is null.
445     *
446     * @param extensions a collection of stanza extensions
447     */
448    // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
449    public final void addExtensions(Collection<? extends XmlElement> extensions) {
450        if (extensions == null) return;
451        for (XmlElement packetExtension : extensions) {
452            addExtension(packetExtension);
453        }
454    }
455
456    /**
457     * Check if a stanza extension with the given element and namespace exists.
458     * <p>
459     * The argument <code>elementName</code> may be null.
460     * </p>
461     *
462     * @param elementName TODO javadoc me please
463     * @param namespace TODO javadoc me please
464     * @return true if a stanza extension exists, false otherwise.
465     */
466    public final boolean hasExtension(String elementName, String namespace) {
467        if (elementName == null) {
468            return hasExtension(namespace);
469        }
470        QName key = new QName(namespace, elementName);
471        synchronized (extensionElements) {
472            return extensionElements.containsKey(key);
473        }
474    }
475
476    // Overridden in order to avoid an extra copy.
477    @Override
478    public final boolean hasExtension(String namespace) {
479        synchronized (extensionElements) {
480            for (XmlElement packetExtension : extensionElements.values()) {
481                if (packetExtension.getNamespace().equals(namespace)) {
482                    return true;
483                }
484            }
485        }
486        return false;
487    }
488
489    /**
490     * Remove the stanza extension with the given elementName and namespace.
491     *
492     * @param elementName TODO javadoc me please
493     * @param namespace TODO javadoc me please
494     * @return the removed stanza extension or null.
495     */
496    // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
497    public final XmlElement removeExtension(String elementName, String namespace) {
498        QName key = new QName(namespace, elementName);
499        synchronized (extensionElements) {
500            return extensionElements.remove(key);
501        }
502    }
503
504    /**
505     * Removes a stanza extension from the packet.
506     *
507     * @param extension the stanza extension to remove.
508     * @return the removed stanza extension or null.
509     * @deprecated use {@link StanzaBuilder} instead.
510     */
511    @Deprecated
512    // TODO: Remove in Smack 4.5.
513    public final XmlElement removeExtension(XmlElement extension)  {
514        QName key = extension.getQName();
515        synchronized (extensionElements) {
516            List<XmlElement> list = extensionElements.getAll(key);
517            boolean removed = list.remove(extension);
518            if (removed) {
519                return extension;
520            }
521        }
522        return null;
523    }
524
525    /**
526     * Returns a short String describing the Stanza. This method is suited for log purposes.
527     */
528    @Override
529    public abstract String toString();
530
531    @Override
532    public final String getNamespace() {
533        return namespace;
534    }
535
536    /**
537     * Returns the default language used for all messages containing localized content.
538     *
539     * @return the default language
540     */
541    public static String getDefaultLanguage() {
542        return DEFAULT_LANGUAGE;
543    }
544
545    /**
546     * Add to, from, and id attributes.
547     *
548     * @param xml the {@link XmlStringBuilder}.
549     */
550    protected final void addCommonAttributes(XmlStringBuilder xml) {
551        xml.optAttribute("to", getTo());
552        xml.optAttribute("from", getFrom());
553        xml.optAttribute("id", getStanzaId());
554    }
555
556    protected void logCommonAttributes(StringBuilder sb) {
557        if (getTo() != null) {
558            sb.append("to=").append(to).append(',');
559        }
560        if (getFrom() != null) {
561            sb.append("from=").append(from).append(',');
562        }
563        if (hasStanzaIdSet()) {
564            sb.append("id=").append(id).append(',');
565        }
566    }
567
568    /**
569     * Append an XMPPError is this stanza has one set.
570     *
571     * @param xml the XmlStringBuilder to append the error to.
572     */
573    protected final void appendErrorIfExists(XmlStringBuilder xml) {
574        StanzaError error = getError();
575        if (error != null) {
576            xml.append(error);
577        }
578    }
579
580    /**
581     * Return the provided non-empty language, or use this {@link XmlLangElement} language (if set).
582     *
583     * @param language the provided language, may be the empty string or <code>null</code>.
584     * @return the provided language or this element's language (if set).
585     */
586    static String determineLanguage(XmlLangElement xmlLangElement, String language) {
587        if (language != null && !language.isEmpty()) {
588            return language;
589        }
590
591        return xmlLangElement.getLanguage();
592    }
593}