StreamNegotiator.java

  1. /**
  2.  *
  3.  * Copyright 2003-2006 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.filetransfer;

  18. import org.jivesoftware.smack.SmackException;
  19. import org.jivesoftware.smack.SmackException.NoResponseException;
  20. import org.jivesoftware.smack.SmackException.NotConnectedException;
  21. import org.jivesoftware.smack.XMPPConnection;
  22. import org.jivesoftware.smack.XMPPException;
  23. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  24. import org.jivesoftware.smack.packet.IQ;
  25. import org.jivesoftware.smack.packet.Stanza;
  26. import org.jivesoftware.smack.util.EventManger;
  27. import org.jivesoftware.smack.util.EventManger.Callback;
  28. import org.jivesoftware.smackx.si.packet.StreamInitiation;
  29. import org.jivesoftware.smackx.xdata.FormField;
  30. import org.jivesoftware.smackx.xdata.packet.DataForm;
  31. import org.jxmpp.jid.Jid;

  32. import java.io.InputStream;
  33. import java.io.OutputStream;

  34. /**
  35.  * After the file transfer negotiation process is completed according to
  36.  * XEP-0096, the negotiation process is passed off to a particular stream
  37.  * negotiator. The stream negotiator will then negotiate the chosen stream and
  38.  * return the stream to transfer the file.
  39.  *
  40.  * @author Alexander Wenckus
  41.  */
  42. public abstract class StreamNegotiator {

  43.     /**
  44.      * A event manager for stream initiation requests send to us.
  45.      * <p>
  46.      * Those are typical XEP-45 Open or XEP-65 Bytestream IQ requests. The even key is in the format
  47.      * "initiationFrom + '\t' + streamId"
  48.      * </p>
  49.      */
  50.     // TODO This field currently being static is considered a quick hack. Ideally this should take
  51.     // the local connection into account, for example by changing the key to
  52.     // "localJid + '\t' + initiationFrom + '\t' + streamId" or making the field non-static (but then
  53.     // you need to provide access to the InitiationListeners, which could get tricky)
  54.     protected static final EventManger<String, IQ, SmackException.NotConnectedException> initationSetEvents = new EventManger<>();

  55.     /**
  56.      * Creates the initiation acceptance packet to forward to the stream
  57.      * initiator.
  58.      *
  59.      * @param streamInitiationOffer The offer from the stream initiator to connect for a stream.
  60.      * @param namespaces            The namespace that relates to the accepted means of transfer.
  61.      * @return The response to be forwarded to the initiator.
  62.      */
  63.     protected static StreamInitiation createInitiationAccept(
  64.             StreamInitiation streamInitiationOffer, String[] namespaces)
  65.     {
  66.         StreamInitiation response = new StreamInitiation();
  67.         response.setTo(streamInitiationOffer.getFrom());
  68.         response.setFrom(streamInitiationOffer.getTo());
  69.         response.setType(IQ.Type.result);
  70.         response.setStanzaId(streamInitiationOffer.getStanzaId());

  71.         DataForm form = new DataForm(DataForm.Type.submit);
  72.         FormField field = new FormField(
  73.                 FileTransferNegotiator.STREAM_DATA_FIELD_NAME);
  74.         for (String namespace : namespaces) {
  75.             field.addValue(namespace);
  76.         }
  77.         form.addField(field);

  78.         response.setFeatureNegotiationForm(form);
  79.         return response;
  80.     }

  81.     protected final IQ initiateIncomingStream(final XMPPConnection connection, StreamInitiation initiation)
  82.                    throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  83.         final StreamInitiation response = createInitiationAccept(initiation,
  84.                 getNamespaces());

  85.         newStreamInitiation(initiation.getFrom(), initiation.getSessionID());

  86.         final String eventKey = initiation.getFrom().toString() + '\t' + initiation.getSessionID();
  87.         IQ streamMethodInitiation;
  88.         try {
  89.             streamMethodInitiation = initationSetEvents.performActionAndWaitForEvent(eventKey, connection.getPacketReplyTimeout(), new Callback<NotConnectedException>() {
  90.                 @Override
  91.                 public void action() throws NotConnectedException {
  92.                     try {
  93.                         connection.sendStanza(response);
  94.                     }
  95.                     catch (InterruptedException e) {
  96.                         // Ignore
  97.                     }
  98.                 }
  99.             });
  100.         }
  101.         catch (InterruptedException e) {
  102.             // TODO remove this try/catch once merged into 4.2's master branch
  103.             throw new IllegalStateException(e);
  104.         }

  105.         if (streamMethodInitiation == null) {
  106.             throw NoResponseException.newWith(connection);
  107.         }
  108.         XMPPErrorException.ifHasErrorThenThrow(streamMethodInitiation);
  109.         return streamMethodInitiation;
  110.     }

  111.     /**
  112.      * Signal that a new stream initiation arrived. The negotiator may needs to prepare for it.
  113.      *
  114.      * @param from     The initiator of the file transfer.
  115.      * @param streamID The stream ID related to the transfer.
  116.      */
  117.     protected abstract void newStreamInitiation(Jid from, String streamID);


  118.     abstract InputStream negotiateIncomingStream(Stanza streamInitiation) throws XMPPErrorException,
  119.             InterruptedException, NoResponseException, SmackException;

  120.     /**
  121.      * This method handles the file stream download negotiation process. The
  122.      * appropriate stream negotiator's initiate incoming stream is called after
  123.      * an appropriate file transfer method is selected. The manager will respond
  124.      * to the initiator with the selected means of transfer, then it will handle
  125.      * any negotiation specific to the particular transfer method. This method
  126.      * returns the InputStream, ready to transfer the file.
  127.      *
  128.      * @param initiation The initiation that triggered this download.
  129.      * @return After the negotiation process is complete, the InputStream to
  130.      *         write a file to is returned.
  131.      * @throws XMPPErrorException If an error occurs during this process an XMPPException is
  132.      *                       thrown.
  133.      * @throws InterruptedException If thread is interrupted.
  134.      * @throws SmackException
  135.      */
  136.     public abstract InputStream createIncomingStream(StreamInitiation initiation)
  137.             throws XMPPErrorException, InterruptedException, NoResponseException, SmackException;

  138.     /**
  139.      * This method handles the file upload stream negotiation process. The
  140.      * particular stream negotiator is determined during the file transfer
  141.      * negotiation process. This method returns the OutputStream to transmit the
  142.      * file to the remote user.
  143.      *
  144.      * @param streamID  The streamID that uniquely identifies the file transfer.
  145.      * @param initiator The fully-qualified JID of the initiator of the file transfer.
  146.      * @param target    The fully-qualified JID of the target or receiver of the file
  147.      *                  transfer.
  148.      * @return The negotiated stream ready for data.
  149.      * @throws XMPPErrorException If an error occurs during the negotiation process an
  150.      *                       exception will be thrown.
  151.      * @throws SmackException
  152.      * @throws XMPPException
  153.      * @throws InterruptedException
  154.      */
  155.     public abstract OutputStream createOutgoingStream(String streamID,
  156.             Jid initiator, Jid target) throws XMPPErrorException, NoResponseException, SmackException, XMPPException, InterruptedException;

  157.     /**
  158.      * Returns the XMPP namespace reserved for this particular type of file
  159.      * transfer.
  160.      *
  161.      * @return Returns the XMPP namespace reserved for this particular type of
  162.      *         file transfer.
  163.      */
  164.     public abstract String[] getNamespaces();

  165.     public static void signal(String eventKey, IQ eventValue) {
  166.         initationSetEvents.signalEvent(eventKey, eventValue);
  167.     }
  168. }