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 &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<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}