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 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<ExtensionElement> 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 ExtensionElement getExtension(QName qname) { 231 return extensionElements.getFirst(qname); 232 } 233 234 @Override 235 public final List<ExtensionElement> getExtensions() { 236 return extensionElements.values(); 237 } 238 239 @Override 240 public final List<ExtensionElement> 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}