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