001/** 002 * 003 * Copyright 2003-2006 Jive Software. 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.smackx.si.packet; 018 019import java.util.Date; 020 021import org.jivesoftware.smack.packet.ExtensionElement; 022import org.jivesoftware.smack.packet.IQ; 023import org.jivesoftware.smack.util.StringUtils; 024 025import org.jivesoftware.smackx.xdata.packet.DataForm; 026 027import org.jxmpp.util.XmppDateTime; 028 029/** 030 * The process by which two entities initiate a stream. 031 * 032 * @author Alexander Wenckus 033 */ 034public class StreamInitiation extends IQ { 035 036 public static final String ELEMENT = "si"; 037 public static final String NAMESPACE = "http://jabber.org/protocol/si"; 038 039 private String id; 040 041 private String mimeType; 042 043 private File file; 044 045 private Feature featureNegotiation; 046 047 public StreamInitiation() { 048 super(ELEMENT, NAMESPACE); 049 } 050 051 /** 052 * The "id" attribute is an opaque identifier. This attribute MUST be 053 * present on type='set', and MUST be a valid string. This SHOULD NOT be 054 * sent back on type='result', since the <iq/> "id" attribute provides the 055 * only context needed. This value is generated by the Sender, and the same 056 * value MUST be used throughout a session when talking to the Receiver. 057 * 058 * @param id The "id" attribute. 059 */ 060 public void setSessionID(final String id) { 061 this.id = id; 062 } 063 064 /** 065 * Uniquely identifies a stream initiation to the recipient. 066 * 067 * @return The "id" attribute. 068 * @see #setSessionID(String) 069 */ 070 public String getSessionID() { 071 return id; 072 } 073 074 /** 075 * The "mime-type" attribute identifies the MIME-type for the data across 076 * the stream. This attribute MUST be a valid MIME-type as registered with 077 * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as 078 * listed at <http://www.iana.org/assignments/media-types>). During 079 * negotiation, this attribute SHOULD be present, and is otherwise not 080 * required. If not included during negotiation, its value is assumed to be 081 * "binary/octet-stream". 082 * 083 * @param mimeType The valid mime-type. 084 */ 085 public void setMimeType(final String mimeType) { 086 this.mimeType = mimeType; 087 } 088 089 /** 090 * Identifies the type of file that is desired to be transferred. 091 * 092 * @return The mime-type. 093 * @see #setMimeType(String) 094 */ 095 public String getMimeType() { 096 return mimeType; 097 } 098 099 /** 100 * Sets the file which contains the information pertaining to the file to be 101 * transferred. 102 * 103 * @param file The file identified by the stream initiator to be sent. 104 */ 105 public void setFile(final File file) { 106 this.file = file; 107 } 108 109 /** 110 * Returns the file containing the information about the request. 111 * 112 * @return Returns the file containing the information about the request. 113 */ 114 public File getFile() { 115 return file; 116 } 117 118 /** 119 * Sets the data form which contains the valid methods of stream negotiation 120 * and transfer. 121 * 122 * @param form The dataform containing the methods. 123 */ 124 public void setFeatureNegotiationForm(final DataForm form) { 125 this.featureNegotiation = new Feature(form); 126 } 127 128 /** 129 * Returns the data form which contains the valid methods of stream 130 * negotiation and transfer. 131 * 132 * @return Returns the data form which contains the valid methods of stream 133 * negotiation and transfer. 134 */ 135 public DataForm getFeatureNegotiationForm() { 136 return featureNegotiation.getData(); 137 } 138 139 @Override 140 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) { 141 switch (getType()) { 142 case set: 143 buf.optAttribute("id", getSessionID()); 144 buf.optAttribute("mime-type", getMimeType()); 145 buf.attribute("profile", NAMESPACE + "/profile/file-transfer"); 146 buf.rightAngleBracket(); 147 148 // Add the file section if there is one. 149 buf.optElement(file); 150 break; 151 case result: 152 buf.rightAngleBracket(); 153 break; 154 default: 155 throw new IllegalArgumentException("IQ Type not understood"); 156 } 157 if (featureNegotiation != null) { 158 buf.append(featureNegotiation.toXML()); 159 } 160 return buf; 161 } 162 163 /** 164 * <ul> 165 * <li>size: The size, in bytes, of the data to be sent.</li> 166 * <li>name: The name of the file that the Sender wishes to send.</li> 167 * <li>date: The last modification time of the file. This is specified 168 * using the DateTime profile as described in Jabber Date and Time Profiles.</li> 169 * <li>hash: The MD5 sum of the file contents.</li> 170 * </ul> 171 * <p> 172 * <desc> is used to provide a sender-generated description of the 173 * file so the receiver can better understand what is being sent. It MUST 174 * NOT be sent in the result. 175 * </p> 176 * <p> 177 * When <range> is sent in the offer, it should have no attributes. 178 * This signifies that the sender can do ranged transfers. When a Stream 179 * Initiation result is sent with the <range> element, it uses these 180 * attributes: 181 * </p> 182 * <ul> 183 * <li>offset: Specifies the position, in bytes, to start transferring the 184 * file data from. This defaults to zero (0) if not specified.</li> 185 * <li>length - Specifies the number of bytes to retrieve starting at 186 * offset. This defaults to the length of the file from offset to the end.</li> 187 * </ul> 188 * Both attributes are OPTIONAL on the <range> element. Sending no 189 * attributes is synonymous with not sending the <range> element. When 190 * no <range> element is sent in the Stream Initiation result, the 191 * Sender MUST send the complete file starting at offset 0. More generally, 192 * data is sent over the stream byte for byte starting at the offset 193 * position for the length specified. 194 * 195 * @author Alexander Wenckus 196 */ 197 public static class File implements ExtensionElement { 198 199 private final String name; 200 201 private final long size; 202 203 private String hash; 204 205 private Date date; 206 207 private String desc; 208 209 private boolean isRanged; 210 211 /** 212 * Constructor providing the name of the file and its size. 213 * 214 * @param name The name of the file. 215 * @param size The size of the file in bytes. 216 */ 217 public File(final String name, final long size) { 218 if (name == null) { 219 throw new NullPointerException("name cannot be null"); 220 } 221 222 this.name = name; 223 this.size = size; 224 } 225 226 /** 227 * Returns the file's name. 228 * 229 * @return Returns the file's name. 230 */ 231 public String getName() { 232 return name; 233 } 234 235 /** 236 * Returns the file's size. 237 * 238 * @return Returns the file's size. 239 */ 240 public long getSize() { 241 return size; 242 } 243 244 /** 245 * Sets the MD5 sum of the file's contents. 246 * 247 * @param hash The MD5 sum of the file's contents. 248 */ 249 public void setHash(final String hash) { 250 this.hash = hash; 251 } 252 253 /** 254 * Returns the MD5 sum of the file's contents. 255 * 256 * @return Returns the MD5 sum of the file's contents 257 */ 258 public String getHash() { 259 return hash; 260 } 261 262 /** 263 * Sets the date that the file was last modified. 264 * 265 * @param date The date that the file was last modified. 266 */ 267 public void setDate(Date date) { 268 this.date = date; 269 } 270 271 /** 272 * Returns the date that the file was last modified. 273 * 274 * @return Returns the date that the file was last modified. 275 */ 276 public Date getDate() { 277 return date; 278 } 279 280 /** 281 * Sets the description of the file. 282 * 283 * @param desc The description of the file so that the file receiver can 284 * know what file it is. 285 */ 286 public void setDesc(final String desc) { 287 this.desc = desc; 288 } 289 290 /** 291 * Returns the description of the file. 292 * 293 * @return Returns the description of the file. 294 */ 295 public String getDesc() { 296 return desc; 297 } 298 299 /** 300 * True if a range can be provided and false if it cannot. 301 * 302 * @param isRanged True if a range can be provided and false if it cannot. 303 */ 304 public void setRanged(final boolean isRanged) { 305 this.isRanged = isRanged; 306 } 307 308 /** 309 * Returns whether or not the initiator can support a range for the file 310 * transfer. 311 * 312 * @return Returns whether or not the initiator can support a range for 313 * the file transfer. 314 */ 315 public boolean isRanged() { 316 return isRanged; 317 } 318 319 @Override 320 public String getElementName() { 321 return "file"; 322 } 323 324 @Override 325 public String getNamespace() { 326 return "http://jabber.org/protocol/si/profile/file-transfer"; 327 } 328 329 @Override 330 public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 331 StringBuilder buffer = new StringBuilder(); 332 333 buffer.append('<').append(getElementName()).append(" xmlns=\"") 334 .append(getNamespace()).append("\" "); 335 336 if (getName() != null) { 337 buffer.append("name=\"").append(StringUtils.escapeForXmlAttribute(getName())).append("\" "); 338 } 339 340 if (getSize() > 0) { 341 buffer.append("size=\"").append(getSize()).append("\" "); 342 } 343 344 if (getDate() != null) { 345 buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" "); 346 } 347 348 if (getHash() != null) { 349 buffer.append("hash=\"").append(getHash()).append("\" "); 350 } 351 352 if ((desc != null && desc.length() > 0) || isRanged) { 353 buffer.append('>'); 354 if (getDesc() != null && desc.length() > 0) { 355 buffer.append("<desc>").append(StringUtils.escapeForXmlText(getDesc())).append("</desc>"); 356 } 357 if (isRanged()) { 358 buffer.append("<range/>"); 359 } 360 buffer.append("</").append(getElementName()).append('>'); 361 } 362 else { 363 buffer.append("/>"); 364 } 365 return buffer.toString(); 366 } 367 } 368 369 /** 370 * The feature negotiation portion of the StreamInitiation packet. 371 * 372 * @author Alexander Wenckus 373 * 374 */ 375 public static class Feature implements ExtensionElement { 376 377 private final DataForm data; 378 379 /** 380 * The dataform can be provided as part of the constructor. 381 * 382 * @param data The dataform. 383 */ 384 public Feature(final DataForm data) { 385 this.data = data; 386 } 387 388 /** 389 * Returns the dataform associated with the feature negotiation. 390 * 391 * @return Returns the dataform associated with the feature negotiation. 392 */ 393 public DataForm getData() { 394 return data; 395 } 396 397 @Override 398 public String getNamespace() { 399 return "http://jabber.org/protocol/feature-neg"; 400 } 401 402 @Override 403 public String getElementName() { 404 return "feature"; 405 } 406 407 @Override 408 public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 409 StringBuilder buf = new StringBuilder(); 410 buf 411 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); 412 buf.append(data.toXML()); 413 buf.append("</feature>"); 414 return buf.toString(); 415 } 416 } 417}