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/octect-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 transfered. 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 * transfered. 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 neotiation 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 * neotiation and transfer. 131 * 132 * @return Returns the data form which contains the valid methods of stream 133 * neotiation 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.optAppend(file.toXML()); 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 * <p/> 173 * <desc> is used to provide a sender-generated description of the 174 * file so the receiver can better understand what is being sent. It MUST 175 * NOT be sent in the result. 176 * <p/> 177 * <p/> 178 * When <range> is sent in the offer, it should have no attributes. 179 * This signifies that the sender can do ranged transfers. When a Stream 180 * Initiation result is sent with the <range> element, it uses these 181 * attributes: 182 * <p/> 183 * <ul> 184 * <li>offset: Specifies the position, in bytes, to start transferring the 185 * file data from. This defaults to zero (0) if not specified.</li> 186 * <li>length - Specifies the number of bytes to retrieve starting at 187 * offset. This defaults to the length of the file from offset to the end.</li> 188 * </ul> 189 * <p/> 190 * <p/> 191 * Both attributes are OPTIONAL on the <range> element. Sending no 192 * attributes is synonymous with not sending the <range> element. When 193 * no <range> element is sent in the Stream Initiation result, the 194 * Sender MUST send the complete file starting at offset 0. More generally, 195 * data is sent over the stream byte for byte starting at the offset 196 * position for the length specified. 197 * 198 * @author Alexander Wenckus 199 */ 200 public static class File implements ExtensionElement { 201 202 private final String name; 203 204 private final long size; 205 206 private String hash; 207 208 private Date date; 209 210 private String desc; 211 212 private boolean isRanged; 213 214 /** 215 * Constructor providing the name of the file and its size. 216 * 217 * @param name The name of the file. 218 * @param size The size of the file in bytes. 219 */ 220 public File(final String name, final long size) { 221 if (name == null) { 222 throw new NullPointerException("name cannot be null"); 223 } 224 225 this.name = name; 226 this.size = size; 227 } 228 229 /** 230 * Returns the file's name. 231 * 232 * @return Returns the file's name. 233 */ 234 public String getName() { 235 return name; 236 } 237 238 /** 239 * Returns the file's size. 240 * 241 * @return Returns the file's size. 242 */ 243 public long getSize() { 244 return size; 245 } 246 247 /** 248 * Sets the MD5 sum of the file's contents. 249 * 250 * @param hash The MD5 sum of the file's contents. 251 */ 252 public void setHash(final String hash) { 253 this.hash = hash; 254 } 255 256 /** 257 * Returns the MD5 sum of the file's contents. 258 * 259 * @return Returns the MD5 sum of the file's contents 260 */ 261 public String getHash() { 262 return hash; 263 } 264 265 /** 266 * Sets the date that the file was last modified. 267 * 268 * @param date The date that the file was last modified. 269 */ 270 public void setDate(Date date) { 271 this.date = date; 272 } 273 274 /** 275 * Returns the date that the file was last modified. 276 * 277 * @return Returns the date that the file was last modified. 278 */ 279 public Date getDate() { 280 return date; 281 } 282 283 /** 284 * Sets the description of the file. 285 * 286 * @param desc The description of the file so that the file reciever can 287 * know what file it is. 288 */ 289 public void setDesc(final String desc) { 290 this.desc = desc; 291 } 292 293 /** 294 * Returns the description of the file. 295 * 296 * @return Returns the description of the file. 297 */ 298 public String getDesc() { 299 return desc; 300 } 301 302 /** 303 * True if a range can be provided and false if it cannot. 304 * 305 * @param isRanged True if a range can be provided and false if it cannot. 306 */ 307 public void setRanged(final boolean isRanged) { 308 this.isRanged = isRanged; 309 } 310 311 /** 312 * Returns whether or not the initiator can support a range for the file 313 * tranfer. 314 * 315 * @return Returns whether or not the initiator can support a range for 316 * the file tranfer. 317 */ 318 public boolean isRanged() { 319 return isRanged; 320 } 321 322 @Override 323 public String getElementName() { 324 return "file"; 325 } 326 327 @Override 328 public String getNamespace() { 329 return "http://jabber.org/protocol/si/profile/file-transfer"; 330 } 331 332 @Override 333 public String toXML() { 334 StringBuilder buffer = new StringBuilder(); 335 336 buffer.append('<').append(getElementName()).append(" xmlns=\"") 337 .append(getNamespace()).append("\" "); 338 339 if (getName() != null) { 340 buffer.append("name=\"").append(StringUtils.escapeForXmlAttribute(getName())).append("\" "); 341 } 342 343 if (getSize() > 0) { 344 buffer.append("size=\"").append(getSize()).append("\" "); 345 } 346 347 if (getDate() != null) { 348 buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" "); 349 } 350 351 if (getHash() != null) { 352 buffer.append("hash=\"").append(getHash()).append("\" "); 353 } 354 355 if ((desc != null && desc.length() > 0) || isRanged) { 356 buffer.append('>'); 357 if (getDesc() != null && desc.length() > 0) { 358 buffer.append("<desc>").append(StringUtils.escapeForXmlText(getDesc())).append("</desc>"); 359 } 360 if (isRanged()) { 361 buffer.append("<range/>"); 362 } 363 buffer.append("</").append(getElementName()).append('>'); 364 } 365 else { 366 buffer.append("/>"); 367 } 368 return buffer.toString(); 369 } 370 } 371 372 /** 373 * The feature negotiation portion of the StreamInitiation packet. 374 * 375 * @author Alexander Wenckus 376 * 377 */ 378 public static class Feature implements ExtensionElement { 379 380 private final DataForm data; 381 382 /** 383 * The dataform can be provided as part of the constructor. 384 * 385 * @param data The dataform. 386 */ 387 public Feature(final DataForm data) { 388 this.data = data; 389 } 390 391 /** 392 * Returns the dataform associated with the feature negotiation. 393 * 394 * @return Returns the dataform associated with the feature negotiation. 395 */ 396 public DataForm getData() { 397 return data; 398 } 399 400 @Override 401 public String getNamespace() { 402 return "http://jabber.org/protocol/feature-neg"; 403 } 404 405 @Override 406 public String getElementName() { 407 return "feature"; 408 } 409 410 @Override 411 public String toXML() { 412 StringBuilder buf = new StringBuilder(); 413 buf 414 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); 415 buf.append(data.toXML()); 416 buf.append("</feature>"); 417 return buf.toString(); 418 } 419 } 420}