| /** |
| * $RCSfile$ |
| * $Revision$ |
| * $Date$ |
| * |
| * Copyright 2003-2006 Jive Software. |
| * |
| * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jivesoftware.smackx.packet; |
| |
| import java.util.Date; |
| |
| import org.jivesoftware.smack.packet.IQ; |
| import org.jivesoftware.smack.packet.PacketExtension; |
| import org.jivesoftware.smack.util.StringUtils; |
| |
| /** |
| * The process by which two entities initiate a stream. |
| * |
| * @author Alexander Wenckus |
| */ |
| public class StreamInitiation extends IQ { |
| |
| private String id; |
| |
| private String mimeType; |
| |
| private File file; |
| |
| private Feature featureNegotiation; |
| |
| /** |
| * The "id" attribute is an opaque identifier. This attribute MUST be |
| * present on type='set', and MUST be a valid string. This SHOULD NOT be |
| * sent back on type='result', since the <iq/> "id" attribute provides the |
| * only context needed. This value is generated by the Sender, and the same |
| * value MUST be used throughout a session when talking to the Receiver. |
| * |
| * @param id The "id" attribute. |
| */ |
| public void setSesssionID(final String id) { |
| this.id = id; |
| } |
| |
| /** |
| * Uniquely identifies a stream initiation to the recipient. |
| * |
| * @return The "id" attribute. |
| * @see #setSesssionID(String) |
| */ |
| public String getSessionID() { |
| return id; |
| } |
| |
| /** |
| * The "mime-type" attribute identifies the MIME-type for the data across |
| * the stream. This attribute MUST be a valid MIME-type as registered with |
| * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as |
| * listed at <http://www.iana.org/assignments/media-types>). During |
| * negotiation, this attribute SHOULD be present, and is otherwise not |
| * required. If not included during negotiation, its value is assumed to be |
| * "binary/octect-stream". |
| * |
| * @param mimeType The valid mime-type. |
| */ |
| public void setMimeType(final String mimeType) { |
| this.mimeType = mimeType; |
| } |
| |
| /** |
| * Identifies the type of file that is desired to be transfered. |
| * |
| * @return The mime-type. |
| * @see #setMimeType(String) |
| */ |
| public String getMimeType() { |
| return mimeType; |
| } |
| |
| /** |
| * Sets the file which contains the information pertaining to the file to be |
| * transfered. |
| * |
| * @param file The file identified by the stream initiator to be sent. |
| */ |
| public void setFile(final File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Returns the file containing the information about the request. |
| * |
| * @return Returns the file containing the information about the request. |
| */ |
| public File getFile() { |
| return file; |
| } |
| |
| /** |
| * Sets the data form which contains the valid methods of stream neotiation |
| * and transfer. |
| * |
| * @param form The dataform containing the methods. |
| */ |
| public void setFeatureNegotiationForm(final DataForm form) { |
| this.featureNegotiation = new Feature(form); |
| } |
| |
| /** |
| * Returns the data form which contains the valid methods of stream |
| * neotiation and transfer. |
| * |
| * @return Returns the data form which contains the valid methods of stream |
| * neotiation and transfer. |
| */ |
| public DataForm getFeatureNegotiationForm() { |
| return featureNegotiation.getData(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.jivesoftware.smack.packet.IQ#getChildElementXML() |
| */ |
| public String getChildElementXML() { |
| StringBuilder buf = new StringBuilder(); |
| if (this.getType().equals(IQ.Type.SET)) { |
| buf.append("<si xmlns=\"http://jabber.org/protocol/si\" "); |
| if (getSessionID() != null) { |
| buf.append("id=\"").append(getSessionID()).append("\" "); |
| } |
| if (getMimeType() != null) { |
| buf.append("mime-type=\"").append(getMimeType()).append("\" "); |
| } |
| buf |
| .append("profile=\"http://jabber.org/protocol/si/profile/file-transfer\">"); |
| |
| // Add the file section if there is one. |
| String fileXML = file.toXML(); |
| if (fileXML != null) { |
| buf.append(fileXML); |
| } |
| } |
| else if (this.getType().equals(IQ.Type.RESULT)) { |
| buf.append("<si xmlns=\"http://jabber.org/protocol/si\">"); |
| } |
| else { |
| throw new IllegalArgumentException("IQ Type not understood"); |
| } |
| if (featureNegotiation != null) { |
| buf.append(featureNegotiation.toXML()); |
| } |
| buf.append("</si>"); |
| return buf.toString(); |
| } |
| |
| /** |
| * <ul> |
| * <li>size: The size, in bytes, of the data to be sent.</li> |
| * <li>name: The name of the file that the Sender wishes to send.</li> |
| * <li>date: The last modification time of the file. This is specified |
| * using the DateTime profile as described in Jabber Date and Time Profiles.</li> |
| * <li>hash: The MD5 sum of the file contents.</li> |
| * </ul> |
| * <p/> |
| * <p/> |
| * <desc> is used to provide a sender-generated description of the |
| * file so the receiver can better understand what is being sent. It MUST |
| * NOT be sent in the result. |
| * <p/> |
| * <p/> |
| * When <range> is sent in the offer, it should have no attributes. |
| * This signifies that the sender can do ranged transfers. When a Stream |
| * Initiation result is sent with the <range> element, it uses these |
| * attributes: |
| * <p/> |
| * <ul> |
| * <li>offset: Specifies the position, in bytes, to start transferring the |
| * file data from. This defaults to zero (0) if not specified.</li> |
| * <li>length - Specifies the number of bytes to retrieve starting at |
| * offset. This defaults to the length of the file from offset to the end.</li> |
| * </ul> |
| * <p/> |
| * <p/> |
| * Both attributes are OPTIONAL on the <range> element. Sending no |
| * attributes is synonymous with not sending the <range> element. When |
| * no <range> element is sent in the Stream Initiation result, the |
| * Sender MUST send the complete file starting at offset 0. More generally, |
| * data is sent over the stream byte for byte starting at the offset |
| * position for the length specified. |
| * |
| * @author Alexander Wenckus |
| */ |
| public static class File implements PacketExtension { |
| |
| private final String name; |
| |
| private final long size; |
| |
| private String hash; |
| |
| private Date date; |
| |
| private String desc; |
| |
| private boolean isRanged; |
| |
| /** |
| * Constructor providing the name of the file and its size. |
| * |
| * @param name The name of the file. |
| * @param size The size of the file in bytes. |
| */ |
| public File(final String name, final long size) { |
| if (name == null) { |
| throw new NullPointerException("name cannot be null"); |
| } |
| |
| this.name = name; |
| this.size = size; |
| } |
| |
| /** |
| * Returns the file's name. |
| * |
| * @return Returns the file's name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Returns the file's size. |
| * |
| * @return Returns the file's size. |
| */ |
| public long getSize() { |
| return size; |
| } |
| |
| /** |
| * Sets the MD5 sum of the file's contents |
| * |
| * @param hash The MD5 sum of the file's contents. |
| */ |
| public void setHash(final String hash) { |
| this.hash = hash; |
| } |
| |
| /** |
| * Returns the MD5 sum of the file's contents |
| * |
| * @return Returns the MD5 sum of the file's contents |
| */ |
| public String getHash() { |
| return hash; |
| } |
| |
| /** |
| * Sets the date that the file was last modified. |
| * |
| * @param date The date that the file was last modified. |
| */ |
| public void setDate(Date date) { |
| this.date = date; |
| } |
| |
| /** |
| * Returns the date that the file was last modified. |
| * |
| * @return Returns the date that the file was last modified. |
| */ |
| public Date getDate() { |
| return date; |
| } |
| |
| /** |
| * Sets the description of the file. |
| * |
| * @param desc The description of the file so that the file reciever can |
| * know what file it is. |
| */ |
| public void setDesc(final String desc) { |
| this.desc = desc; |
| } |
| |
| /** |
| * Returns the description of the file. |
| * |
| * @return Returns the description of the file. |
| */ |
| public String getDesc() { |
| return desc; |
| } |
| |
| /** |
| * True if a range can be provided and false if it cannot. |
| * |
| * @param isRanged True if a range can be provided and false if it cannot. |
| */ |
| public void setRanged(final boolean isRanged) { |
| this.isRanged = isRanged; |
| } |
| |
| /** |
| * Returns whether or not the initiator can support a range for the file |
| * tranfer. |
| * |
| * @return Returns whether or not the initiator can support a range for |
| * the file tranfer. |
| */ |
| public boolean isRanged() { |
| return isRanged; |
| } |
| |
| public String getElementName() { |
| return "file"; |
| } |
| |
| public String getNamespace() { |
| return "http://jabber.org/protocol/si/profile/file-transfer"; |
| } |
| |
| public String toXML() { |
| StringBuilder buffer = new StringBuilder(); |
| |
| buffer.append("<").append(getElementName()).append(" xmlns=\"") |
| .append(getNamespace()).append("\" "); |
| |
| if (getName() != null) { |
| buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); |
| } |
| |
| if (getSize() > 0) { |
| buffer.append("size=\"").append(getSize()).append("\" "); |
| } |
| |
| if (getDate() != null) { |
| buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); |
| } |
| |
| if (getHash() != null) { |
| buffer.append("hash=\"").append(getHash()).append("\" "); |
| } |
| |
| if ((desc != null && desc.length() > 0) || isRanged) { |
| buffer.append(">"); |
| if (getDesc() != null && desc.length() > 0) { |
| buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>"); |
| } |
| if (isRanged()) { |
| buffer.append("<range/>"); |
| } |
| buffer.append("</").append(getElementName()).append(">"); |
| } |
| else { |
| buffer.append("/>"); |
| } |
| return buffer.toString(); |
| } |
| } |
| |
| /** |
| * The feature negotiation portion of the StreamInitiation packet. |
| * |
| * @author Alexander Wenckus |
| * |
| */ |
| public class Feature implements PacketExtension { |
| |
| private final DataForm data; |
| |
| /** |
| * The dataform can be provided as part of the constructor. |
| * |
| * @param data The dataform. |
| */ |
| public Feature(final DataForm data) { |
| this.data = data; |
| } |
| |
| /** |
| * Returns the dataform associated with the feature negotiation. |
| * |
| * @return Returns the dataform associated with the feature negotiation. |
| */ |
| public DataForm getData() { |
| return data; |
| } |
| |
| public String getNamespace() { |
| return "http://jabber.org/protocol/feature-neg"; |
| } |
| |
| public String getElementName() { |
| return "feature"; |
| } |
| |
| public String toXML() { |
| StringBuilder buf = new StringBuilder(); |
| buf |
| .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); |
| buf.append(data.toXML()); |
| buf.append("</feature>"); |
| return buf.toString(); |
| } |
| } |
| } |