StanzaBuilder.java

  1. /**
  2.  *
  3.  * Copyright 2019-2021 Florian Schmaus
  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 java.util.Collection;
  19. import java.util.List;

  20. import javax.xml.namespace.QName;

  21. import org.jivesoftware.smack.packet.id.StanzaIdSource;
  22. import org.jivesoftware.smack.util.Function;
  23. import org.jivesoftware.smack.util.MultiMap;
  24. import org.jivesoftware.smack.util.StringUtils;
  25. import org.jivesoftware.smack.util.ToStringUtil;
  26. import org.jivesoftware.smack.util.XmppElementUtil;

  27. import org.jxmpp.jid.Jid;
  28. import org.jxmpp.jid.impl.JidCreate;
  29. import org.jxmpp.stringprep.XmppStringprepException;

  30. public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements StanzaView {

  31.     final StanzaIdSource stanzaIdSource;
  32.     final String stanzaId;

  33.     Jid to;
  34.     Jid from;

  35.     StanzaError stanzaError;

  36.     String language;

  37.     MultiMap<QName, XmlElement> extensionElements = new MultiMap<>();

  38.     protected StanzaBuilder(StanzaBuilder<?> other) {
  39.         stanzaIdSource = other.stanzaIdSource;
  40.         stanzaId = other.stanzaId;

  41.         to = other.to;
  42.         from = other.from;
  43.         stanzaError = other.stanzaError;
  44.         language = other.language;
  45.         extensionElements = other.extensionElements.clone();
  46.     }

  47.     protected StanzaBuilder(StanzaIdSource stanzaIdSource) {
  48.         this.stanzaIdSource = stanzaIdSource;
  49.         this.stanzaId = null;
  50.     }

  51.     protected StanzaBuilder(String stanzaId) {
  52.         this.stanzaIdSource = null;
  53.         this.stanzaId = StringUtils.requireNullOrNotEmpty(stanzaId, "Stanza ID must not be the empty String");
  54.     }

  55.     protected StanzaBuilder(Stanza message, String stanzaId) {
  56.         this(stanzaId);
  57.         copyFromStanza(message);
  58.     }

  59.     protected StanzaBuilder(Stanza message, StanzaIdSource stanzaIdSource) {
  60.         this(stanzaIdSource);
  61.         copyFromStanza(message);
  62.     }

  63.     private void copyFromStanza(Stanza stanza) {
  64.         to = stanza.getTo();
  65.         from = stanza.getFrom();
  66.         stanzaError = stanza.getError();
  67.         language = stanza.getLanguage();

  68.         extensionElements = stanza.cloneExtensionsMap();
  69.     }

  70.     /**
  71.      * Set the recipient address of the stanza.
  72.      *
  73.      * @param to whom the stanza is being sent.
  74.      * @return a reference to this builder.
  75.      * @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
  76.      * @see #to(Jid)
  77.      */
  78.     public final B to(CharSequence to) throws XmppStringprepException {
  79.         return to(JidCreate.from(to));
  80.     }

  81.     /**
  82.      * Sets who the stanza is being sent "to". The XMPP protocol often makes the "to" attribute optional, so it does not
  83.      * always need to be set.
  84.      *
  85.      * @param to who the stanza is being sent to.
  86.      * @return a reference to this builder.
  87.      */
  88.     public final B to(Jid to) {
  89.         this.to = to;
  90.         return getThis();
  91.     }

  92.     /**
  93.      * Sets who the stanza is being sent "from".
  94.      *
  95.      * @param from who the stanza is being sent from.
  96.      * @return a reference to this builder.
  97.      * @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
  98.      * @see #from(Jid)
  99.      */
  100.     public final B from(CharSequence from) throws XmppStringprepException {
  101.         return from(JidCreate.from(from));
  102.     }

  103.     /**
  104.      * Sets who the stanza is being sent "from". The XMPP protocol often makes the "from" attribute optional, so it does
  105.      * not always need to be set.
  106.      *
  107.      * @param from who the stanza is being sent from.
  108.      * @return a reference to this builder.
  109.      */
  110.     public final B from(Jid from) {
  111.         this.from = from;
  112.         return getThis();
  113.     }

  114.     /**
  115.      * Sets the error for this stanza.
  116.      *
  117.      * @param stanzaError the error to associate with this stanza.
  118.      * @return a reference to this builder.
  119.      */
  120.     public final B setError(StanzaError stanzaError) {
  121.         this.stanzaError = stanzaError;
  122.         return getThis();
  123.     }

  124.     /**
  125.      * Sets the xml:lang for this stanza.
  126.      *
  127.      * @param language the xml:lang of this stanza.
  128.      * @return a reference to this builder.
  129.      */
  130.     public final B setLanguage(String language) {
  131.         this.language = language;
  132.         return getThis();
  133.     }

  134.     public final B addExtension(XmlElement extensionElement) {
  135.         QName key = extensionElement.getQName();
  136.         extensionElements.put(key, extensionElement);
  137.         return getThis();
  138.     }

  139.     public final B addOptExtensions(Collection<? extends XmlElement> extensionElements) {
  140.         if (extensionElements == null) {
  141.             return getThis();
  142.         }

  143.         return addExtensions(extensionElements);
  144.     }

  145.     public final B addExtensions(Collection<? extends XmlElement> extensionElements) {
  146.         for (XmlElement extensionElement : extensionElements) {
  147.             addExtension(extensionElement);
  148.         }
  149.         return getThis();
  150.     }

  151.     public final B overrideExtension(XmlElement extensionElement) {
  152.         QName key = extensionElement.getQName();
  153.         extensionElements.remove(key);
  154.         extensionElements.put(key, extensionElement);
  155.         return getThis();
  156.     }

  157.     public final B removeExtension(String elementName, String namespace) {
  158.         QName key = new QName(namespace, elementName);
  159.         extensionElements.remove(key);
  160.         return getThis();
  161.     }

  162.     public final B removeExtension(ExtensionElement extension) {
  163.         QName key = extension.getQName();
  164.         List<XmlElement> list = extensionElements.getAll(key);
  165.         list.remove(extension);
  166.         return getThis();
  167.     }

  168.     public abstract Stanza build();

  169.     public abstract B getThis();

  170.     @Override
  171.     public final String getStanzaId() {
  172.         return stanzaId;
  173.     }

  174.     @Override
  175.     public final Jid getTo() {
  176.         return to;
  177.     }

  178.     @Override
  179.     public final Jid getFrom() {
  180.         return from;
  181.     }

  182.     @Override
  183.     public final String getLanguage() {
  184.         return language;
  185.     }

  186.     @Override
  187.     public final StanzaError getError() {
  188.         return stanzaError;
  189.     }

  190.     @Override
  191.     public final XmlElement getExtension(QName qname) {
  192.         return extensionElements.getFirst(qname);
  193.     }

  194.     @Override
  195.     public final List<XmlElement> getExtensions() {
  196.         return extensionElements.values();
  197.     }

  198.     @Override
  199.     public final List<XmlElement> getExtensions(QName qname) {
  200.         return extensionElements.getAll(qname);
  201.     }

  202.     @Override
  203.     public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
  204.         return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
  205.     }

  206.     public final boolean willBuildStanzaWithId() {
  207.         return stanzaIdSource != null || StringUtils.isNotEmpty(stanzaId);
  208.     }

  209.     public final void throwIfNoStanzaId() {
  210.         if (willBuildStanzaWithId()) {
  211.             return;
  212.         }
  213.         throw new IllegalArgumentException(
  214.                         "The builder will not build a stanza with an ID set, although it is required");
  215.     }

  216.     protected abstract void addStanzaSpecificAttributes(ToStringUtil.Builder builder);

  217.     @Override
  218.     public final String toString() {
  219.         ToStringUtil.Builder builder = ToStringUtil.builderFor(getClass())
  220.             .addValue("id", stanzaId)
  221.             .addValue("from", from)
  222.             .addValue("to", to)
  223.             .addValue("language", language)
  224.             .addValue("error", stanzaError)
  225.             ;

  226.         addStanzaSpecificAttributes(builder);

  227.         builder.add("Extension Elements", extensionElements.values(), e -> {
  228.             return e.getQName();
  229.         });

  230.         return builder.build();
  231.     }

  232.     public static MessageBuilder buildMessage() {
  233.         return buildMessage(null);
  234.     }

  235.     public static MessageBuilder buildMessage(String stanzaId) {
  236.         return new MessageBuilder(stanzaId);
  237.     }

  238.     public static MessageBuilder buildMessageFrom(Message message, String stanzaId) {
  239.         return new MessageBuilder(message, stanzaId);
  240.     }

  241.     public static MessageBuilder buildMessageFrom(Message message, StanzaIdSource stanzaIdSource) {
  242.         return new MessageBuilder(message, stanzaIdSource);
  243.     }

  244.     public static PresenceBuilder buildPresence() {
  245.         return buildPresence(null);
  246.     }

  247.     public static PresenceBuilder buildPresence(String stanzaId) {
  248.         return new PresenceBuilder(stanzaId);
  249.     }

  250.     public static PresenceBuilder buildPresenceFrom(Presence presence, String stanzaId) {
  251.         return new PresenceBuilder(presence, stanzaId);
  252.     }

  253.     public static PresenceBuilder buildPresenceFrom(Presence presence, StanzaIdSource stanzaIdSource) {
  254.         return new PresenceBuilder(presence, stanzaIdSource);
  255.     }

  256.     public static IqData buildIqData(String stanzaId) {
  257.         return new IqData(stanzaId);
  258.     }

  259.     public static <SB extends StanzaBuilder<?>> SB buildResponse(StanzaView request, Function<SB, String> builderFromStanzaId) {
  260.         SB responseBuilder = builderFromStanzaId.apply(request.getStanzaId());

  261.         responseBuilder.to(request.getFrom())
  262.             .from(request.getTo())
  263.             ;

  264.         return responseBuilder;
  265.     }

  266. }