Home | History | Annotate | Download | only in packet
      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  */
     20 package org.jivesoftware.smackx.packet;
     21 
     22 import java.util.Date;
     23 
     24 import org.jivesoftware.smack.packet.IQ;
     25 import org.jivesoftware.smack.packet.PacketExtension;
     26 import org.jivesoftware.smack.util.StringUtils;
     27 
     28 /**
     29  * The process by which two entities initiate a stream.
     30  *
     31  * @author Alexander Wenckus
     32  */
     33 public 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