001/**
002 *
003 * Copyright 2019-2021 Florian Schmaus
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 */
017package org.jivesoftware.smack.packet;
018
019import java.util.Collection;
020import java.util.List;
021
022import javax.xml.namespace.QName;
023
024import org.jivesoftware.smack.packet.id.StanzaIdSource;
025import org.jivesoftware.smack.util.Function;
026import org.jivesoftware.smack.util.MultiMap;
027import org.jivesoftware.smack.util.StringUtils;
028import org.jivesoftware.smack.util.ToStringUtil;
029import org.jivesoftware.smack.util.XmppElementUtil;
030
031import org.jxmpp.jid.Jid;
032import org.jxmpp.jid.impl.JidCreate;
033import org.jxmpp.stringprep.XmppStringprepException;
034
035public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements StanzaView {
036
037    final StanzaIdSource stanzaIdSource;
038    final String stanzaId;
039
040    Jid to;
041    Jid from;
042
043    StanzaError stanzaError;
044
045    String language;
046
047    MultiMap<QName, XmlElement> extensionElements = new MultiMap<>();
048
049    protected StanzaBuilder(StanzaBuilder<?> other) {
050        stanzaIdSource = other.stanzaIdSource;
051        stanzaId = other.stanzaId;
052
053        to = other.to;
054        from = other.from;
055        stanzaError = other.stanzaError;
056        language = other.language;
057        extensionElements = other.extensionElements.clone();
058    }
059
060    protected StanzaBuilder(StanzaIdSource stanzaIdSource) {
061        this.stanzaIdSource = stanzaIdSource;
062        this.stanzaId = null;
063    }
064
065    protected StanzaBuilder(String stanzaId) {
066        this.stanzaIdSource = null;
067        this.stanzaId = StringUtils.requireNullOrNotEmpty(stanzaId, "Stanza ID must not be the empty String");
068    }
069
070    protected StanzaBuilder(Stanza message, String stanzaId) {
071        this(stanzaId);
072        copyFromStanza(message);
073    }
074
075    protected StanzaBuilder(Stanza message, StanzaIdSource stanzaIdSource) {
076        this(stanzaIdSource);
077        copyFromStanza(message);
078    }
079
080    private void copyFromStanza(Stanza stanza) {
081        to = stanza.getTo();
082        from = stanza.getFrom();
083        stanzaError = stanza.getError();
084        language = stanza.getLanguage();
085
086        extensionElements = stanza.cloneExtensionsMap();
087    }
088
089    /**
090     * Set the recipent address of the stanza.
091     *
092     * @param to whoe the stanza is being sent to.
093     * @return a reference to this builder.
094     * @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
095     * @see #to(Jid)
096     */
097    public final B to(CharSequence to) throws XmppStringprepException {
098        return to(JidCreate.from(to));
099    }
100
101    /**
102     * Sets who the stanza is being sent "to". The XMPP protocol often makes the "to" attribute optional, so it does not
103     * always need to be set.
104     *
105     * @param to who the stanza is being sent to.
106     * @return a reference to this builder.
107     */
108    public final B to(Jid to) {
109        this.to = to;
110        return getThis();
111    }
112
113    /**
114     * Sets who the stanza is being sent "from".
115     *
116     * @param from who the stanza is being sent from.
117     * @return a reference to this builder.
118     * @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
119     * @see #from(Jid)
120     */
121    public final B from(CharSequence from) throws XmppStringprepException {
122        return from(JidCreate.from(from));
123    }
124
125    /**
126     * Sets who the stanza is being sent "from". The XMPP protocol often makes the "from" attribute optional, so it does
127     * not always need to be set.
128     *
129     * @param from who the stanza is being sent from.
130     * @return a reference to this builder.
131     */
132    public final B from(Jid from) {
133        this.from = from;
134        return getThis();
135    }
136
137    /**
138     * Sets the error for this stanza.
139     *
140     * @param stanzaError the error to associate with this stanza.
141     * @return a reference to this builder.
142     */
143    public final B setError(StanzaError stanzaError) {
144        this.stanzaError = stanzaError;
145        return getThis();
146    }
147
148    /**
149     * Sets the xml:lang for this stanza.
150     *
151     * @param language the xml:lang of this stanza.
152     * @return a reference to this builder.
153     */
154    public final B setLanguage(String language) {
155        this.language = language;
156        return getThis();
157    }
158
159    public final B addExtension(XmlElement extensionElement) {
160        QName key = extensionElement.getQName();
161        extensionElements.put(key, extensionElement);
162        return getThis();
163    }
164
165    public final B addOptExtensions(Collection<? extends XmlElement> extensionElements) {
166        if (extensionElements == null) {
167            return getThis();
168        }
169
170        return addExtensions(extensionElements);
171    }
172
173    public final B addExtensions(Collection<? extends XmlElement> extensionElements) {
174        for (XmlElement extensionElement : extensionElements) {
175            addExtension(extensionElement);
176        }
177        return getThis();
178    }
179
180    public final B overrideExtension(XmlElement extensionElement) {
181        QName key = extensionElement.getQName();
182        extensionElements.remove(key);
183        extensionElements.put(key, extensionElement);
184        return getThis();
185    }
186
187    public final B removeExtension(String elementName, String namespace) {
188        QName key = new QName(namespace, elementName);
189        extensionElements.remove(key);
190        return getThis();
191    }
192
193    public final B removeExtension(ExtensionElement extension) {
194        QName key = extension.getQName();
195        List<XmlElement> list = extensionElements.getAll(key);
196        list.remove(extension);
197        return getThis();
198    }
199
200    public abstract Stanza build();
201
202    public abstract B getThis();
203
204    @Override
205    public final String getStanzaId() {
206        return stanzaId;
207    }
208
209    @Override
210    public final Jid getTo() {
211        return to;
212    }
213
214    @Override
215    public final Jid getFrom() {
216        return from;
217    }
218
219    @Override
220    public final String getLanguage() {
221        return language;
222    }
223
224    @Override
225    public final StanzaError getError() {
226        return stanzaError;
227    }
228
229    @Override
230    public final XmlElement getExtension(QName qname) {
231        return extensionElements.getFirst(qname);
232    }
233
234    @Override
235    public final List<XmlElement> getExtensions() {
236        return extensionElements.values();
237    }
238
239    @Override
240    public final List<XmlElement> getExtensions(QName qname) {
241        return extensionElements.getAll(qname);
242    }
243
244    @Override
245    public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
246        return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
247    }
248
249    public final boolean willBuildStanzaWithId() {
250        return stanzaIdSource != null || StringUtils.isNotEmpty(stanzaId);
251    }
252
253    public final void throwIfNoStanzaId() {
254        if (willBuildStanzaWithId()) {
255            return;
256        }
257        throw new IllegalArgumentException(
258                        "The builder will not build a stanza with an ID set, although it is required");
259    }
260
261    protected abstract void addStanzaSpecificAttributes(ToStringUtil.Builder builder);
262
263    @Override
264    public final String toString() {
265        ToStringUtil.Builder builder = ToStringUtil.builderFor(getClass())
266            .addValue("id", stanzaId)
267            .addValue("from", from)
268            .addValue("to", to)
269            .addValue("language", language)
270            .addValue("error", stanzaError)
271            ;
272
273        addStanzaSpecificAttributes(builder);
274
275        builder.add("Extension Elements", extensionElements.values(), e -> {
276            return e.getQName();
277        });
278
279        return builder.build();
280    }
281
282    public static MessageBuilder buildMessage() {
283        return buildMessage(null);
284    }
285
286    public static MessageBuilder buildMessage(String stanzaId) {
287        return new MessageBuilder(stanzaId);
288    }
289
290    public static MessageBuilder buildMessageFrom(Message message, String stanzaId) {
291        return new MessageBuilder(message, stanzaId);
292    }
293
294    public static MessageBuilder buildMessageFrom(Message message, StanzaIdSource stanzaIdSource) {
295        return new MessageBuilder(message, stanzaIdSource);
296    }
297
298    public static PresenceBuilder buildPresence() {
299        return buildPresence(null);
300    }
301
302    public static PresenceBuilder buildPresence(String stanzaId) {
303        return new PresenceBuilder(stanzaId);
304    }
305
306    public static PresenceBuilder buildPresenceFrom(Presence presence, String stanzaId) {
307        return new PresenceBuilder(presence, stanzaId);
308    }
309
310    public static PresenceBuilder buildPresenceFrom(Presence presence, StanzaIdSource stanzaIdSource) {
311        return new PresenceBuilder(presence, stanzaIdSource);
312    }
313
314    public static IqData buildIqData(String stanzaId) {
315        return new IqData(stanzaId);
316    }
317
318    public static <SB extends StanzaBuilder<?>> SB buildResponse(StanzaView request, Function<SB, String> builderFromStanzaId) {
319        SB responseBuilder = builderFromStanzaId.apply(request.getStanzaId());
320
321        responseBuilder.to(request.getFrom())
322            .from(request.getTo())
323            ;
324
325        return responseBuilder;
326    }
327
328}