001/** 002 * 003 * Copyright 2003-2007 Jive Software, 2014-2022 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 */ 017 018package org.jivesoftware.smackx.jingle.element; 019 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023 024import org.jivesoftware.smack.XMPPConnection; 025import org.jivesoftware.smack.packet.IQ; 026import org.jivesoftware.smack.packet.IqBuilder; 027import org.jivesoftware.smack.packet.IqData; 028import org.jivesoftware.smack.packet.id.StandardStanzaIdSource; 029import org.jivesoftware.smack.util.Objects; 030import org.jivesoftware.smack.util.StringUtils; 031 032import org.jxmpp.jid.FullJid; 033 034/** 035 * The Jingle element. 036 * 037 * <h2>Jingle Element Structure</h2> 038 * <pre>{@code 039 * jingle 040 * │ action (REQUIRED, XEP-0166 § 7.2) 041 * | content-accept 042 * | content-add 043 * | content-modify 044 * | content-reject 045 * | content-remove 046 * | description-info 047 * | security-info 048 * | session-accept 049 * | session-info 050 * | session-initiate 051 * | transport-accept 052 * | transport-info 053 * | transport-reject 054 * | transport-replace 055 * │ initiator (RECOMMENDED for session initiate, NOT RECOMMENDED otherwise, full JID, XEP-0166 § 7.1) 056 * │ responder (RECOMMENDED for session accept, NOT RECOMMENDED otherwise, full JID. XEP-0166 § 7.1) 057 * │ sid (REQUIRED, SHOULD match XML Nmtoken production) 058 * │ 059 * ├── <reason/> (optional, XEP-0166 § 7.4) 060 * │ │ 061 * │ └──(alternative─session│busy│..) 062 * │ 063 * └── <content/> (one or more, XEP-0166 § 7.3) 064 * │ creator (REQUIRED, must be one of) 065 * | initiator 066 * | responder 067 * │ disposition (OPTIONAL) 068 * │ name (REQUIRED) 069 * │ senders (OPTIONAL, except when content-modify then REQUIRED) 070 * | both (default) 071 * | initiator 072 * | none 073 * | responder 074 * │ 075 * ├──description 076 * │ │ media 077 * │ │ xmlns 078 * │ │ 079 * │ ├──payload─type 080 * │ │ 081 * │ └──file (XEP─0234) 082 * │ 083 * └──transport 084 * │ xmlns 085 * │ pwd (OPTIONAL, XEP-0176 Jingle ICE) 086 * │ ufrag (OPTIONAL, XEP-0176 Jingle ICE) 087 * │ mode (XEP-0234 Jingle File Transfer) 088 * │ sid (XEP-0234 Jingle File Transfer) 089 * │ 090 * └──candidate 091 * component 092 * foundation 093 * generation 094 * id 095 * ip 096 * network 097 * port 098 * priority 099 * protocol 100 * type 101 * }</pre> 102 * 103 * @author Florian Schmaus 104 */ 105public final class Jingle extends IQ { 106 107 public static final String NAMESPACE = "urn:xmpp:jingle:1"; 108 109 public static final String ACTION_ATTRIBUTE_NAME = "action"; 110 111 public static final String INITIATOR_ATTRIBUTE_NAME = "initiator"; 112 113 public static final String RESPONDER_ATTRIBUTE_NAME = "responder"; 114 115 public static final String SESSION_ID_ATTRIBUTE_NAME = "sid"; 116 117 public static final String ELEMENT = "jingle"; 118 119 /** 120 * The session ID related to this session. The session ID is a unique identifier generated by the initiator. This 121 * should match the XML Nmtoken production so that XML character escaping is not needed for characters such as &. 122 */ 123 private final String sessionId; 124 125 /** 126 * The jingle action. This attribute is required. 127 */ 128 private final JingleAction action; 129 130 private final FullJid initiator; 131 132 private final FullJid responder; 133 134 private final JingleReason reason; 135 136 private final List<JingleContent> contents; 137 138 private Jingle(Builder builder, String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason, 139 List<JingleContent> contents) { 140 super(builder, ELEMENT, NAMESPACE); 141 this.sessionId = StringUtils.requireNotNullNorEmpty(sessionId, "Jingle session ID must not be null"); 142 this.action = Objects.requireNonNull(action, "Jingle action must not be null"); 143 this.initiator = initiator; 144 this.responder = responder; 145 this.reason = reason; 146 if (contents != null) { 147 this.contents = Collections.unmodifiableList(contents); 148 } 149 else { 150 this.contents = Collections.emptyList(); 151 } 152 setType(Type.set); 153 } 154 155 /** 156 * Get the initiator. The initiator will be the full JID of the entity that has initiated the flow (which may be 157 * different to the "from" address in the IQ) 158 * 159 * @return the initiator 160 */ 161 public FullJid getInitiator() { 162 return initiator; 163 } 164 165 /** 166 * Get the responder. The responder is the full JID of the entity that has replied to the initiation (which may be 167 * different to the "to" address in the IQ). 168 * 169 * @return the responder 170 */ 171 public FullJid getResponder() { 172 return responder; 173 } 174 175 /** 176 * Returns the session ID related to the session. The session ID is a unique identifier generated by the initiator. 177 * This should match the XML Nmtoken production so that XML character escaping is not needed for characters such as 178 * &. 179 * 180 * @return Returns the session ID related to the session. 181 */ 182 public String getSid() { 183 return sessionId; 184 } 185 186 /** 187 * Get the action specified in the jingle IQ. 188 * 189 * @return the action. 190 */ 191 public JingleAction getAction() { 192 return action; 193 } 194 195 public JingleReason getReason() { 196 return reason; 197 } 198 199 /** 200 * Get a List of the contents. 201 * 202 * @return the contents. 203 */ 204 public List<JingleContent> getContents() { 205 return contents; 206 } 207 208 /** 209 * Get the only jingle content if one exists, or <code>null</code>. This method will throw an 210 * {@link IllegalStateException} if there is more than one jingle content. 211 * 212 * @return a JingleContent instance or <code>null</code>. 213 * @throws IllegalStateException if there is more than one jingle content. 214 */ 215 public JingleContent getSoleContentOrThrow() { 216 if (contents.isEmpty()) { 217 return null; 218 } 219 220 if (contents.size() > 1) { 221 throw new IllegalStateException(); 222 } 223 224 return contents.get(0); 225 } 226 227 @Override 228 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { 229 xml.optAttribute(INITIATOR_ATTRIBUTE_NAME, getInitiator()); 230 xml.optAttribute(RESPONDER_ATTRIBUTE_NAME, getResponder()); 231 xml.optAttribute(ACTION_ATTRIBUTE_NAME, getAction()); 232 xml.optAttribute(SESSION_ID_ATTRIBUTE_NAME, getSid()); 233 xml.rightAngleBracket(); 234 235 xml.optElement(reason); 236 237 xml.append(contents); 238 239 return xml; 240 } 241 242 /** 243 * Deprecated, do not use. 244 * 245 * @return a builder. 246 * @deprecated use {@link #builder(XMPPConnection)} instead. 247 */ 248 @Deprecated 249 // TODO: Remove in Smack 4.6. 250 public static Builder getBuilder() { 251 return builder(StandardStanzaIdSource.DEFAULT.getNewStanzaId()); 252 } 253 254 public static Builder builder(XMPPConnection connection) { 255 return new Builder(connection); 256 } 257 258 public static Builder builder(IqData iqData) { 259 return new Builder(iqData); 260 } 261 262 public static Builder builder(String stanzaId) { 263 return new Builder(stanzaId); 264 } 265 266 public static final class Builder extends IqBuilder<Builder, Jingle> { 267 private String sid; 268 269 private JingleAction action; 270 271 private FullJid initiator; 272 273 private FullJid responder; 274 275 private JingleReason reason; 276 277 private List<JingleContent> contents; 278 279 Builder(IqData iqCommon) { 280 super(iqCommon); 281 } 282 283 Builder(XMPPConnection connection) { 284 super(connection); 285 } 286 287 Builder(String stanzaId) { 288 super(stanzaId); 289 } 290 291 public Builder setSessionId(String sessionId) { 292 StringUtils.requireNotNullNorEmpty(sessionId, "Session ID must not be null nor empty"); 293 this.sid = sessionId; 294 return this; 295 } 296 297 public Builder setAction(JingleAction action) { 298 this.action = action; 299 return this; 300 } 301 302 public Builder setInitiator(FullJid initator) { 303 this.initiator = initator; 304 return this; 305 } 306 307 public Builder setResponder(FullJid responder) { 308 this.responder = responder; 309 return this; 310 } 311 312 public Builder addJingleContent(JingleContent content) { 313 if (contents == null) { 314 contents = new ArrayList<>(1); 315 } 316 contents.add(content); 317 return this; 318 } 319 320 public Builder setReason(JingleReason.Reason reason) { 321 this.reason = new JingleReason(reason); 322 return this; 323 } 324 325 public Builder setReason(JingleReason reason) { 326 this.reason = reason; 327 return this; 328 } 329 330 @Override 331 public Jingle build() { 332 return new Jingle(this, sid, action, initiator, responder, reason, contents); 333 } 334 335 @Override 336 public Builder getThis() { 337 return this; 338 } 339 } 340}