/** | |
* $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(); | |
} | |
} | |
} |