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