blob: 511a02c0d7edf107ae93957e4ce39da7fae7b6c8 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2006 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20package org.jivesoftware.smackx.packet;
21
22import java.util.Date;
23
24import org.jivesoftware.smack.packet.IQ;
25import org.jivesoftware.smack.packet.PacketExtension;
26import org.jivesoftware.smack.util.StringUtils;
27
28/**
29 * The process by which two entities initiate a stream.
30 *
31 * @author Alexander Wenckus
32 */
33public class StreamInitiation extends IQ {
34
35 private String id;
36
37 private String mimeType;
38
39 private File file;
40
41 private Feature featureNegotiation;
42
43 /**
44 * The "id" attribute is an opaque identifier. This attribute MUST be
45 * present on type='set', and MUST be a valid string. This SHOULD NOT be
46 * sent back on type='result', since the <iq/> "id" attribute provides the
47 * only context needed. This value is generated by the Sender, and the same
48 * value MUST be used throughout a session when talking to the Receiver.
49 *
50 * @param id The "id" attribute.
51 */
52 public void setSesssionID(final String id) {
53 this.id = id;
54 }
55
56 /**
57 * Uniquely identifies a stream initiation to the recipient.
58 *
59 * @return The "id" attribute.
60 * @see #setSesssionID(String)
61 */
62 public String getSessionID() {
63 return id;
64 }
65
66 /**
67 * The "mime-type" attribute identifies the MIME-type for the data across
68 * the stream. This attribute MUST be a valid MIME-type as registered with
69 * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as
70 * listed at <http://www.iana.org/assignments/media-types>). During
71 * negotiation, this attribute SHOULD be present, and is otherwise not
72 * required. If not included during negotiation, its value is assumed to be
73 * "binary/octect-stream".
74 *
75 * @param mimeType The valid mime-type.
76 */
77 public void setMimeType(final String mimeType) {
78 this.mimeType = mimeType;
79 }
80
81 /**
82 * Identifies the type of file that is desired to be transfered.
83 *
84 * @return The mime-type.
85 * @see #setMimeType(String)
86 */
87 public String getMimeType() {
88 return mimeType;
89 }
90
91 /**
92 * Sets the file which contains the information pertaining to the file to be
93 * transfered.
94 *
95 * @param file The file identified by the stream initiator to be sent.
96 */
97 public void setFile(final File file) {
98 this.file = file;
99 }
100
101 /**
102 * Returns the file containing the information about the request.
103 *
104 * @return Returns the file containing the information about the request.
105 */
106 public File getFile() {
107 return file;
108 }
109
110 /**
111 * Sets the data form which contains the valid methods of stream neotiation
112 * and transfer.
113 *
114 * @param form The dataform containing the methods.
115 */
116 public void setFeatureNegotiationForm(final DataForm form) {
117 this.featureNegotiation = new Feature(form);
118 }
119
120 /**
121 * Returns the data form which contains the valid methods of stream
122 * neotiation and transfer.
123 *
124 * @return Returns the data form which contains the valid methods of stream
125 * neotiation and transfer.
126 */
127 public DataForm getFeatureNegotiationForm() {
128 return featureNegotiation.getData();
129 }
130
131 /*
132 * (non-Javadoc)
133 *
134 * @see org.jivesoftware.smack.packet.IQ#getChildElementXML()
135 */
136 public String getChildElementXML() {
137 StringBuilder buf = new StringBuilder();
138 if (this.getType().equals(IQ.Type.SET)) {
139 buf.append("<si xmlns=\"http://jabber.org/protocol/si\" ");
140 if (getSessionID() != null) {
141 buf.append("id=\"").append(getSessionID()).append("\" ");
142 }
143 if (getMimeType() != null) {
144 buf.append("mime-type=\"").append(getMimeType()).append("\" ");
145 }
146 buf
147 .append("profile=\"http://jabber.org/protocol/si/profile/file-transfer\">");
148
149 // Add the file section if there is one.
150 String fileXML = file.toXML();
151 if (fileXML != null) {
152 buf.append(fileXML);
153 }
154 }
155 else if (this.getType().equals(IQ.Type.RESULT)) {
156 buf.append("<si xmlns=\"http://jabber.org/protocol/si\">");
157 }
158 else {
159 throw new IllegalArgumentException("IQ Type not understood");
160 }
161 if (featureNegotiation != null) {
162 buf.append(featureNegotiation.toXML());
163 }
164 buf.append("</si>");
165 return buf.toString();
166 }
167
168 /**
169 * <ul>
170 * <li>size: The size, in bytes, of the data to be sent.</li>
171 * <li>name: The name of the file that the Sender wishes to send.</li>
172 * <li>date: The last modification time of the file. This is specified
173 * using the DateTime profile as described in Jabber Date and Time Profiles.</li>
174 * <li>hash: The MD5 sum of the file contents.</li>
175 * </ul>
176 * <p/>
177 * <p/>
178 * &lt;desc&gt; is used to provide a sender-generated description of the
179 * file so the receiver can better understand what is being sent. It MUST
180 * NOT be sent in the result.
181 * <p/>
182 * <p/>
183 * When &lt;range&gt; is sent in the offer, it should have no attributes.
184 * This signifies that the sender can do ranged transfers. When a Stream
185 * Initiation result is sent with the <range> element, it uses these
186 * attributes:
187 * <p/>
188 * <ul>
189 * <li>offset: Specifies the position, in bytes, to start transferring the
190 * file data from. This defaults to zero (0) if not specified.</li>
191 * <li>length - Specifies the number of bytes to retrieve starting at
192 * offset. This defaults to the length of the file from offset to the end.</li>
193 * </ul>
194 * <p/>
195 * <p/>
196 * Both attributes are OPTIONAL on the &lt;range&gt; element. Sending no
197 * attributes is synonymous with not sending the &lt;range&gt; element. When
198 * no &lt;range&gt; element is sent in the Stream Initiation result, the
199 * Sender MUST send the complete file starting at offset 0. More generally,
200 * data is sent over the stream byte for byte starting at the offset
201 * position for the length specified.
202 *
203 * @author Alexander Wenckus
204 */
205 public static class File implements PacketExtension {
206
207 private final String name;
208
209 private final long size;
210
211 private String hash;
212
213 private Date date;
214
215 private String desc;
216
217 private boolean isRanged;
218
219 /**
220 * Constructor providing the name of the file and its size.
221 *
222 * @param name The name of the file.
223 * @param size The size of the file in bytes.
224 */
225 public File(final String name, final long size) {
226 if (name == null) {
227 throw new NullPointerException("name cannot be null");
228 }
229
230 this.name = name;
231 this.size = size;
232 }
233
234 /**
235 * Returns the file's name.
236 *
237 * @return Returns the file's name.
238 */
239 public String getName() {
240 return name;
241 }
242
243 /**
244 * Returns the file's size.
245 *
246 * @return Returns the file's size.
247 */
248 public long getSize() {
249 return size;
250 }
251
252 /**
253 * Sets the MD5 sum of the file's contents
254 *
255 * @param hash The MD5 sum of the file's contents.
256 */
257 public void setHash(final String hash) {
258 this.hash = hash;
259 }
260
261 /**
262 * Returns the MD5 sum of the file's contents
263 *
264 * @return Returns the MD5 sum of the file's contents
265 */
266 public String getHash() {
267 return hash;
268 }
269
270 /**
271 * Sets the date that the file was last modified.
272 *
273 * @param date The date that the file was last modified.
274 */
275 public void setDate(Date date) {
276 this.date = date;
277 }
278
279 /**
280 * Returns the date that the file was last modified.
281 *
282 * @return Returns the date that the file was last modified.
283 */
284 public Date getDate() {
285 return date;
286 }
287
288 /**
289 * Sets the description of the file.
290 *
291 * @param desc The description of the file so that the file reciever can
292 * know what file it is.
293 */
294 public void setDesc(final String desc) {
295 this.desc = desc;
296 }
297
298 /**
299 * Returns the description of the file.
300 *
301 * @return Returns the description of the file.
302 */
303 public String getDesc() {
304 return desc;
305 }
306
307 /**
308 * True if a range can be provided and false if it cannot.
309 *
310 * @param isRanged True if a range can be provided and false if it cannot.
311 */
312 public void setRanged(final boolean isRanged) {
313 this.isRanged = isRanged;
314 }
315
316 /**
317 * Returns whether or not the initiator can support a range for the file
318 * tranfer.
319 *
320 * @return Returns whether or not the initiator can support a range for
321 * the file tranfer.
322 */
323 public boolean isRanged() {
324 return isRanged;
325 }
326
327 public String getElementName() {
328 return "file";
329 }
330
331 public String getNamespace() {
332 return "http://jabber.org/protocol/si/profile/file-transfer";
333 }
334
335 public String toXML() {
336 StringBuilder buffer = new StringBuilder();
337
338 buffer.append("<").append(getElementName()).append(" xmlns=\"")
339 .append(getNamespace()).append("\" ");
340
341 if (getName() != null) {
342 buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" ");
343 }
344
345 if (getSize() > 0) {
346 buffer.append("size=\"").append(getSize()).append("\" ");
347 }
348
349 if (getDate() != null) {
350 buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" ");
351 }
352
353 if (getHash() != null) {
354 buffer.append("hash=\"").append(getHash()).append("\" ");
355 }
356
357 if ((desc != null && desc.length() > 0) || isRanged) {
358 buffer.append(">");
359 if (getDesc() != null && desc.length() > 0) {
360 buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>");
361 }
362 if (isRanged()) {
363 buffer.append("<range/>");
364 }
365 buffer.append("</").append(getElementName()).append(">");
366 }
367 else {
368 buffer.append("/>");
369 }
370 return buffer.toString();
371 }
372 }
373
374 /**
375 * The feature negotiation portion of the StreamInitiation packet.
376 *
377 * @author Alexander Wenckus
378 *
379 */
380 public class Feature implements PacketExtension {
381
382 private final DataForm data;
383
384 /**
385 * The dataform can be provided as part of the constructor.
386 *
387 * @param data The dataform.
388 */
389 public Feature(final DataForm data) {
390 this.data = data;
391 }
392
393 /**
394 * Returns the dataform associated with the feature negotiation.
395 *
396 * @return Returns the dataform associated with the feature negotiation.
397 */
398 public DataForm getData() {
399 return data;
400 }
401
402 public String getNamespace() {
403 return "http://jabber.org/protocol/feature-neg";
404 }
405
406 public String getElementName() {
407 return "feature";
408 }
409
410 public String toXML() {
411 StringBuilder buf = new StringBuilder();
412 buf
413 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">");
414 buf.append(data.toXML());
415 buf.append("</feature>");
416 return buf.toString();
417 }
418 }
419}