1/**
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}
420