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 public String getElementName() { 321 return "file"; 322 } 323 324 public String getNamespace() { 325 return "http://jabber.org/protocol/si/profile/file-transfer"; 326 } 327 328 public String toXML() { 329 StringBuilder buffer = new StringBuilder(); 330 331 buffer.append("<").append(getElementName()).append(" xmlns=\"") 332 .append(getNamespace()).append("\" "); 333 334 if (getName() != null) { 335 buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); 336 } 337 338 if (getSize() > 0) { 339 buffer.append("size=\"").append(getSize()).append("\" "); 340 } 341 342 if (getDate() != null) { 343 buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" "); 344 } 345 346 if (getHash() != null) { 347 buffer.append("hash=\"").append(getHash()).append("\" "); 348 } 349 350 if ((desc != null && desc.length() > 0) || isRanged) { 351 buffer.append(">"); 352 if (getDesc() != null && desc.length() > 0) { 353 buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>"); 354 } 355 if (isRanged()) { 356 buffer.append("<range/>"); 357 } 358 buffer.append("</").append(getElementName()).append(">"); 359 } 360 else { 361 buffer.append("/>"); 362 } 363 return buffer.toString(); 364 } 365 } 366 367 /** 368 * The feature negotiation portion of the StreamInitiation packet. 369 * 370 * @author Alexander Wenckus 371 * 372 */ 373 public class Feature implements ExtensionElement { 374 375 private final DataForm data; 376 377 /** 378 * The dataform can be provided as part of the constructor. 379 * 380 * @param data The dataform. 381 */ 382 public Feature(final DataForm data) { 383 this.data = data; 384 } 385 386 /** 387 * Returns the dataform associated with the feature negotiation. 388 * 389 * @return Returns the dataform associated with the feature negotiation. 390 */ 391 public DataForm getData() { 392 return data; 393 } 394 395 public String getNamespace() { 396 return "http://jabber.org/protocol/feature-neg"; 397 } 398 399 public String getElementName() { 400 return "feature"; 401 } 402 403 public String toXML() { 404 StringBuilder buf = new StringBuilder(); 405 buf 406 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); 407 buf.append(data.toXML()); 408 buf.append("</feature>"); 409 return buf.toString(); 410 } 411 } 412}