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