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