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     * &lt;desc&gt; 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 &lt;range&gt; 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 &lt;range&gt; element. Sending no
190     * attributes is synonymous with not sending the &lt;range&gt; element. When
191     * no &lt;range&gt; 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}