mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-09-27 18:19:33 +02:00
a7b3303f3e
This also resulted in a refactoring of the Providers and parsing Exceptions. NumberFormatException and ParseException can now be thrown directly, the wrapping in a SmackParsingException is down at a higher layer, i.e. in AbstractProvider.
189 lines
8.1 KiB
Java
189 lines
8.1 KiB
Java
/**
|
|
*
|
|
* Copyright 2003-2006 Jive Software.
|
|
*
|
|
* 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.filetransfer;
|
|
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
|
|
import org.jivesoftware.smack.Manager;
|
|
import org.jivesoftware.smack.SmackException;
|
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|
import org.jivesoftware.smack.XMPPConnection;
|
|
import org.jivesoftware.smack.XMPPException;
|
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
|
import org.jivesoftware.smack.packet.IQ;
|
|
import org.jivesoftware.smack.packet.Stanza;
|
|
import org.jivesoftware.smack.util.EventManger;
|
|
|
|
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
|
import org.jivesoftware.smackx.xdata.FormField;
|
|
import org.jivesoftware.smackx.xdata.ListSingleFormField;
|
|
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
|
|
|
import org.jxmpp.jid.Jid;
|
|
|
|
/**
|
|
* After the file transfer negotiation process is completed according to
|
|
* XEP-0096, the negotiation process is passed off to a particular stream
|
|
* negotiator. The stream negotiator will then negotiate the chosen stream and
|
|
* return the stream to transfer the file.
|
|
*
|
|
* @author Alexander Wenckus
|
|
*/
|
|
public abstract class StreamNegotiator extends Manager {
|
|
|
|
protected StreamNegotiator(XMPPConnection connection) {
|
|
super(connection);
|
|
}
|
|
|
|
/**
|
|
* A event manager for stream initiation requests send to us.
|
|
* <p>
|
|
* Those are typical XEP-45 Open or XEP-65 Bytestream IQ requests. The even key is in the format
|
|
* "initiationFrom + '\t' + streamId"
|
|
* </p>
|
|
*/
|
|
// TODO This field currently being static is considered a quick hack. Ideally this should take
|
|
// the local connection into account, for example by changing the key to
|
|
// "localJid + '\t' + initiationFrom + '\t' + streamId" or making the field non-static (but then
|
|
// you need to provide access to the InitiationListeners, which could get tricky)
|
|
protected static final EventManger<String, IQ, SmackException.NotConnectedException> initationSetEvents = new EventManger<>();
|
|
|
|
/**
|
|
* Creates the initiation acceptance stanza to forward to the stream
|
|
* initiator.
|
|
*
|
|
* @param streamInitiationOffer The offer from the stream initiator to connect for a stream.
|
|
* @param namespace The namespace that relates to the accepted means of transfer.
|
|
* @return The response to be forwarded to the initiator.
|
|
*/
|
|
protected static StreamInitiation createInitiationAccept(
|
|
StreamInitiation streamInitiationOffer, String namespace) {
|
|
StreamInitiation response = new StreamInitiation();
|
|
response.setTo(streamInitiationOffer.getFrom());
|
|
response.setFrom(streamInitiationOffer.getTo());
|
|
response.setType(IQ.Type.result);
|
|
response.setStanzaId(streamInitiationOffer.getStanzaId());
|
|
|
|
DataForm.Builder form = DataForm.builder();
|
|
ListSingleFormField.Builder field = FormField.listSingleBuilder(
|
|
FileTransferNegotiator.STREAM_DATA_FIELD_NAME);
|
|
field.setValue(namespace);
|
|
form.addField(field.build());
|
|
|
|
response.setFeatureNegotiationForm(form.build());
|
|
return response;
|
|
}
|
|
|
|
protected final IQ initiateIncomingStream(final XMPPConnection connection, StreamInitiation initiation)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException {
|
|
final StreamInitiation response = createInitiationAccept(initiation,
|
|
getNamespace());
|
|
|
|
newStreamInitiation(initiation.getFrom(), initiation.getSessionID());
|
|
|
|
final String eventKey = initiation.getFrom().toString() + '\t' + initiation.getSessionID();
|
|
IQ streamMethodInitiation;
|
|
try {
|
|
streamMethodInitiation = initationSetEvents.performActionAndWaitForEvent(eventKey, connection.getReplyTimeout(), new EventManger.Callback<NotConnectedException>() {
|
|
@Override
|
|
public void action() throws NotConnectedException {
|
|
try {
|
|
connection.sendStanza(response);
|
|
}
|
|
catch (InterruptedException e) {
|
|
// Ignore
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
// TODO remove this try/catch once merged into 4.2's master branch
|
|
throw new IllegalStateException(e);
|
|
}
|
|
|
|
if (streamMethodInitiation == null) {
|
|
throw NoResponseException.newWith(connection, "stream initiation");
|
|
}
|
|
XMPPErrorException.ifHasErrorThenThrow(streamMethodInitiation);
|
|
return streamMethodInitiation;
|
|
}
|
|
|
|
/**
|
|
* Signal that a new stream initiation arrived. The negotiator may needs to prepare for it.
|
|
*
|
|
* @param from The initiator of the file transfer.
|
|
* @param streamID The stream ID related to the transfer.
|
|
*/
|
|
protected abstract void newStreamInitiation(Jid from, String streamID);
|
|
|
|
|
|
abstract InputStream negotiateIncomingStream(Stanza streamInitiation) throws XMPPErrorException,
|
|
InterruptedException, SmackException;
|
|
|
|
/**
|
|
* This method handles the file stream download negotiation process. The
|
|
* appropriate stream negotiator's initiate incoming stream is called after
|
|
* an appropriate file transfer method is selected. The manager will respond
|
|
* to the initiator with the selected means of transfer, then it will handle
|
|
* any negotiation specific to the particular transfer method. This method
|
|
* returns the InputStream, ready to transfer the file.
|
|
*
|
|
* @param initiation The initiation that triggered this download.
|
|
* @return After the negotiation process is complete, the InputStream to
|
|
* write a file to is returned.
|
|
* @throws XMPPErrorException If an error occurs during this process an XMPPException is
|
|
* thrown.
|
|
* @throws InterruptedException If thread is interrupted.
|
|
* @throws SmackException if Smack detected an exceptional situation.
|
|
*/
|
|
public abstract InputStream createIncomingStream(StreamInitiation initiation)
|
|
throws XMPPErrorException, InterruptedException, SmackException;
|
|
|
|
/**
|
|
* This method handles the file upload stream negotiation process. The
|
|
* particular stream negotiator is determined during the file transfer
|
|
* negotiation process. This method returns the OutputStream to transmit the
|
|
* file to the remote user.
|
|
*
|
|
* @param streamID The streamID that uniquely identifies the file transfer.
|
|
* @param initiator The fully-qualified JID of the initiator of the file transfer.
|
|
* @param target The fully-qualified JID of the target or receiver of the file
|
|
* transfer.
|
|
* @return The negotiated stream ready for data.
|
|
* @throws SmackException if Smack detected an exceptional situation.
|
|
* @throws XMPPException if an XMPP protocol error was received.
|
|
* @throws InterruptedException if the calling thread was interrupted.
|
|
*/
|
|
public abstract OutputStream createOutgoingStream(String streamID,
|
|
Jid initiator, Jid target) throws SmackException, XMPPException, InterruptedException;
|
|
|
|
/**
|
|
* Returns the XMPP namespace reserved for this particular type of file
|
|
* transfer.
|
|
*
|
|
* @return Returns the XMPP namespace reserved for this particular type of
|
|
* file transfer.
|
|
*/
|
|
public abstract String getNamespace();
|
|
|
|
public static void signal(String eventKey, IQ eventValue) {
|
|
initationSetEvents.signalEvent(eventKey, eventValue);
|
|
}
|
|
}
|