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