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}