001/**
002 *
003 * Copyright 2019-2020 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, ExtensionElement> 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 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(ExtensionElement extensionElement) {
160        QName key = extensionElement.getQName();
161        extensionElements.put(key, extensionElement);
162        return getThis();
163    }
164
165    public final B addOptExtensions(Collection<? extends ExtensionElement> extensionElements) {
166        if (extensionElements == null) {
167            return getThis();
168        }
169
170        return addExtensions(extensionElements);
171    }
172
173    public final B addExtensions(Collection<? extends ExtensionElement> extensionElements) {
174        for (ExtensionElement extensionElement : extensionElements) {
175            addExtension(extensionElement);
176        }
177        return getThis();
178    }
179
180    public final B overrideExtension(ExtensionElement extensionElement) {
181        QName key = extensionElement.getQName();
182        extensionElements.remove(key);
183        extensionElements.put(key, extensionElement);
184        return getThis();
185    }
186
187    public abstract Stanza build();
188
189    public abstract B getThis();
190
191    @Override
192    public final String getStanzaId() {
193        return stanzaId;
194    }
195
196    @Override
197    public final Jid getTo() {
198        return to;
199    }
200
201    @Override
202    public final Jid getFrom() {
203        return from;
204    }
205
206    @Override
207    public final String getLanguage() {
208        return language;
209    }
210
211    @Override
212    public final StanzaError getError() {
213        return stanzaError;
214    }
215
216    @Override
217    public final ExtensionElement getExtension(QName qname) {
218        return extensionElements.getFirst(qname);
219    }
220
221    @Override
222    public final List<ExtensionElement> getExtensions() {
223        return extensionElements.values();
224    }
225
226    @Override
227    public final List<ExtensionElement> getExtensions(QName qname) {
228        return extensionElements.getAll(qname);
229    }
230
231    @Override
232    public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
233        return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
234    }
235
236    public final boolean willBuildStanzaWithId() {
237        return stanzaIdSource != null || StringUtils.isNotEmpty(stanzaId);
238    }
239
240    public final void throwIfNoStanzaId() {
241        if (willBuildStanzaWithId()) {
242            return;
243        }
244        throw new IllegalArgumentException(
245                        "The builder will not build a stanza with an ID set, although it is required");
246    }
247
248    protected abstract void addStanzaSpecificAttributes(ToStringUtil.Builder builder);
249
250    @Override
251    public final String toString() {
252        ToStringUtil.Builder builder = ToStringUtil.builderFor(getClass())
253            .addValue("id", stanzaId)
254            .addValue("from", from)
255            .addValue("to", to)
256            .addValue("language", language)
257            .addValue("error", stanzaError)
258            ;
259
260        addStanzaSpecificAttributes(builder);
261
262        builder.add("Extension Elements", extensionElements.values(), e -> {
263            return e.getQName();
264        });
265
266        return builder.build();
267    }
268
269    public static MessageBuilder buildMessage() {
270        return buildMessage(null);
271    }
272
273    public static MessageBuilder buildMessage(String stanzaId) {
274        return new MessageBuilder(stanzaId);
275    }
276
277    public static MessageBuilder buildMessageFrom(Message message, String stanzaId) {
278        return new MessageBuilder(message, stanzaId);
279    }
280
281    public static MessageBuilder buildMessageFrom(Message message, StanzaIdSource stanzaIdSource) {
282        return new MessageBuilder(message, stanzaIdSource);
283    }
284
285    public static PresenceBuilder buildPresence() {
286        return buildPresence(null);
287    }
288
289    public static PresenceBuilder buildPresence(String stanzaId) {
290        return new PresenceBuilder(stanzaId);
291    }
292
293    public static PresenceBuilder buildPresenceFrom(Presence presence, String stanzaId) {
294        return new PresenceBuilder(presence, stanzaId);
295    }
296
297    public static PresenceBuilder buildPresenceFrom(Presence presence, StanzaIdSource stanzaIdSource) {
298        return new PresenceBuilder(presence, stanzaIdSource);
299    }
300
301    public static IqData buildIqData(String stanzaId) {
302        return new IqData(stanzaId);
303    }
304
305    public static <SB extends StanzaBuilder<?>> SB buildResponse(StanzaView request, Function<SB, String> builderFromStanzaId) {
306        SB responseBuilder = builderFromStanzaId.apply(request.getStanzaId());
307
308        responseBuilder.to(request.getFrom())
309            .from(request.getTo())
310            ;
311
312        return responseBuilder;
313    }
314
315}