remote)
- {
- return true;
- }
-
- /**
- * Notifies this TransportManagerJabberImpl that it should conclude
- * any started connectivity establishment.
- *
- * @throws OperationFailedException if anything goes wrong with connectivity
- * establishment (i.e. ICE failed, ...)
- */
- public void wrapupConnectivityEstablishment()
- throws OperationFailedException
- {
- }
-
- /**
- * Removes a content with a specific name from the transport-related part of
- * the session represented by this TransportManagerJabberImpl which
- * may have been reported through previous calls to the
- * startCandidateHarvest and
- * startConnectivityEstablishment methods.
- *
- * Note: Because TransportManager deals with
- * MediaTypes, not content names and
- * TransportManagerJabberImpl does not implement translating from
- * content name to MediaType, implementers are expected to call
- * {@link TransportManager#closeStreamConnector(MediaType)}.
- *
- *
- * @param name the name of the content to be removed from the
- * transport-related part of the session represented by this
- * TransportManagerJabberImpl
- */
- public abstract void removeContent(String name);
-
- /**
- * Removes a content with a specific name from a specific collection of
- * contents and closes any associated StreamConnector.
- *
- * @param contents the collection of contents to remove the content with the
- * specified name from
- * @param name the name of the content to remove
- * @return the removed ContentPacketExtension if any; otherwise,
- * null
- */
- protected ContentPacketExtension removeContent(
- Iterable contents,
- String name)
- {
- for (Iterator contentIter = contents.iterator();
- contentIter.hasNext();)
- {
- ContentPacketExtension content = contentIter.next();
-
- if (name.equals(content.getName()))
- {
- contentIter.remove();
-
- // closeStreamConnector
- RtpDescriptionPacketExtension rtpDescription
- = content.getFirstChildOfType(
- RtpDescriptionPacketExtension.class);
-
- if (rtpDescription != null)
- {
- closeStreamConnector(
- MediaType.parseString(rtpDescription.getMedia()));
- }
-
- return content;
- }
- }
- return null;
- }
-
- private static T clone(T src)
- throws Exception
- {
- @SuppressWarnings("unchecked")
- T dst = (T) src.getClass().newInstance();
-
- // attributes
- for (String name : src.getAttributeNames())
- dst.setAttribute(name, src.getAttribute(name));
- // namespace
- dst.setNamespace(src.getNamespace());
- // text
- dst.setText(src.getText());
- return dst;
- }
-
- static IceUdpTransportPacketExtension cloneTransportAndCandidates(
- IceUdpTransportPacketExtension src)
- throws OperationFailedException
- {
- IceUdpTransportPacketExtension dst = null;
-
- if (src != null)
- {
- try
- {
- dst = clone(src);
-
- for (CandidatePacketExtension srcCand : src.getCandidateList())
- {
- if (!(srcCand instanceof RemoteCandidatePacketExtension))
- dst.addCandidate(clone(srcCand));
- }
- }
- catch (Exception e)
- {
- dst = null;
- ProtocolProviderServiceJabberImpl
- .throwOperationFailedException(
- "Failed to close transport and candidates.",
- OperationFailedException.GENERAL_ERROR,
- e,
- logger);
- }
- }
- return dst;
- }
-
- /**
- * Releases the resources acquired by this TransportManager and
- * prepares it for garbage collection.
- */
- public void close()
- {
- for (MediaType mediaType : MediaType.values())
- closeStreamConnector(mediaType);
- }
-
- /**
- * Closes a specific StreamConnector associated with a specific
- * MediaType. If this TransportManager has a reference to
- * the specified streamConnector, it remains.
- * Also expires the ColibriConferenceIQ.Channel associated with
- * the closed StreamConnector.
- *
- * @param mediaType the MediaType associated with the specified
- * streamConnector
- * @param streamConnector the StreamConnector to be closed
- */
- @Override
- protected void closeStreamConnector(
- MediaType mediaType,
- StreamConnector streamConnector)
- {
- try
- {
- boolean superCloseStreamConnector = true;
-
- if (streamConnector instanceof ColibriStreamConnector)
- {
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- superCloseStreamConnector = false;
- call.closeColibriStreamConnector(
- peer,
- mediaType,
- (ColibriStreamConnector) streamConnector);
- }
- }
- }
- if (superCloseStreamConnector)
- super.closeStreamConnector(mediaType, streamConnector);
- }
- finally
- {
- /*
- * Expire the ColibriConferenceIQ.Channel associated with the closed
- * StreamConnector.
- */
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List channels
- = content.getChannels();
-
- if (channels.size() == 2)
- {
- ColibriConferenceIQ requestConferenceIQ
- = new ColibriConferenceIQ();
-
- requestConferenceIQ.setID(colibri.getID());
-
- ColibriConferenceIQ.Content requestContent
- = requestConferenceIQ.getOrCreateContent(
- content.getName());
-
- requestContent.addChannel(channels.get(1 /* remote */));
-
- /*
- * Regardless of whether the request to expire the
- * Channel associated with mediaType succeeds, consider
- * the Channel in question expired. Since
- * RawUdpTransportManager allocates a single channel per
- * MediaType, consider the whole Content expired.
- */
- colibri.removeContent(content);
-
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- call.expireColibriChannels(
- peer,
- requestConferenceIQ);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * Adds support for telephony conferences utilizing the Jitsi Videobridge
- * server-side technology.
- *
- * @see #doCreateStreamConnector(MediaType)
- */
- @Override
- protected StreamConnector createStreamConnector(final MediaType mediaType)
- throws OperationFailedException
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, true /* local */);
-
- if (channel != null)
- {
- CallPeerJabberImpl peer = getCallPeer();
- CallJabberImpl call = peer.getCall();
- StreamConnector streamConnector
- = call.createColibriStreamConnector(
- peer,
- mediaType,
- channel,
- new StreamConnectorFactory()
- {
- public StreamConnector createStreamConnector()
- {
- try
- {
- return doCreateStreamConnector(mediaType);
- }
- catch (OperationFailedException ofe)
- {
- return null;
- }
- }
- });
-
- if (streamConnector != null)
- return streamConnector;
- }
-
- return doCreateStreamConnector(mediaType);
- }
-
- protected abstract PacketExtension createTransport(String media)
- throws OperationFailedException;
-
- protected PacketExtension createTransportForStartCandidateHarvest(
- String media)
- throws OperationFailedException
- {
- PacketExtension pe = null;
-
- if (getCallPeer().isJitsiVideobridge())
- {
- MediaType mediaType = MediaType.parseString(media);
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, false /* remote */);
-
- if (channel != null)
- pe = cloneTransportAndCandidates(channel.getTransport());
- }
- else
- pe = createTransport(media);
- return pe;
- }
-
- /**
- * Initializes a new PacketExtension instance appropriate to the
- * type of Jingle transport represented by this TransportManager.
- * The new instance is not initialized with any attributes or child
- * extensions.
- *
- * @return a new PacketExtension instance appropriate to the type
- * of Jingle transport represented by this TransportManager
- */
- protected abstract PacketExtension createTransportPacketExtension();
-
- /**
- * Creates a media StreamConnector for a stream of a specific
- * MediaType. The minimum and maximum of the media port boundaries
- * are taken into account.
- *
- * @param mediaType the MediaType of the stream for which a
- * StreamConnector is to be created
- * @return a StreamConnector for the stream of the specified
- * mediaType
- * @throws OperationFailedException if the binding of the sockets fails
- */
- protected StreamConnector doCreateStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- return super.createStreamConnector(mediaType);
- }
-
- /**
- * Finds a TransportManagerJabberImpl participating in a telephony
- * conference utilizing the Jitsi Videobridge server-side technology that
- * this instance is participating in which is establishing the connectivity
- * with the Jitsi Videobridge server (as opposed to a CallPeer).
- *
- * @return a TransportManagerJabberImpl which is participating in
- * a telephony conference utilizing the Jitsi Videobridge server-side
- * technology that this instance is participating in which is establishing
- * the connectivity with the Jitsi Videobridge server (as opposed to a
- * CallPeer).
- */
- TransportManagerJabberImpl
- findTransportManagerEstablishingConnectivityWithJitsiVideobridge()
- {
- Call call = getCallPeer().getCall();
- TransportManagerJabberImpl transportManager = null;
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if ((conference != null) && conference.isJitsiVideobridge())
- {
- for (Call aCall : conference.getCalls())
- {
- Iterator extends CallPeer> callPeerIter
- = aCall.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- CallPeer aCallPeer = callPeerIter.next();
-
- if (aCallPeer instanceof CallPeerJabberImpl)
- {
- TransportManagerJabberImpl aTransportManager
- = ((CallPeerJabberImpl) aCallPeer)
- .getMediaHandler()
- .getTransportManager();
-
- if (aTransportManager
- .isEstablishingConnectivityWithJitsiVideobridge)
- {
- transportManager = aTransportManager;
- break;
- }
- }
- }
- }
- }
- }
- return transportManager;
- }
-
- /**
- * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content
- * associated with a specific MediaType and is to be either locally
- * or remotely used.
- *
- * Note: Modifications to the ColibriConferenceIQ.Channel
- * instance returned by the method propagate to (the state of) this
- * instance.
- *
- *
- * @param mediaType the MediaType associated with the content which
- * contains the ColibriConferenceIQ.Channel to get
- * @param local true if the ColibriConferenceIQ.Channel
- * which is to be used locally is to be returned or false for the
- * one which is to be used remotely
- * @return the ColibriConferenceIQ.Channel which belongs to a
- * content associated with the specified mediaType and which is to
- * be used in accord with the specified local indicator if such a
- * channel exists; otherwise, null
- */
- ColibriConferenceIQ.Channel getColibriChannel(
- MediaType mediaType,
- boolean local)
- {
- ColibriConferenceIQ.Channel channel = null;
-
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List channels
- = content.getChannels();
-
- if (channels.size() == 2)
- channel = channels.get(local ? 0 : 1);
- }
- }
- return channel;
- }
-}
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.net.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * TransportManagers gather local candidates for incoming and outgoing
+ * calls. Their work starts by calling a start method which, using the remote
+ * peer's session description, would start the harvest. Calling a second wrapup
+ * method would deliver the candidate harvest, possibly after blocking if it has
+ * not yet completed.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ */
+public abstract class TransportManagerJabberImpl
+ extends TransportManager
+{
+ /**
+ * The Logger used by the TransportManagerJabberImpl class
+ * and its instances to print debug messages.
+ */
+ private static final Logger logger
+ = Logger.getLogger(TransportManagerJabberImpl.class);
+
+ /**
+ * The ID that we will be assigning to our next candidate. We use
+ * ints for interoperability reasons (Emil: I believe that GTalk
+ * uses ints. If that turns out not to be the case we can stop
+ * using ints here if that's an issue).
+ */
+ private static int nextID = 1;
+
+ /**
+ * The information pertaining to the Jisti Videobridge conference which the
+ * local peer represented by this instance is a focus of. It gives a view of
+ * the whole Jitsi Videobridge conference managed by the associated
+ * CallJabberImpl which provides information specific to this
+ * TransportManager only.
+ */
+ private ColibriConferenceIQ colibri;
+
+ /**
+ * The generation of the candidates we are currently generating
+ */
+ private int currentGeneration = 0;
+
+ /**
+ * The indicator which determines whether this TransportManager
+ * instance is responsible to establish the connectivity with the associated
+ * Jitsi Videobridge (in case it is being employed at all).
+ */
+ boolean isEstablishingConnectivityWithJitsiVideobridge = false;
+
+ /**
+ * The indicator which determines whether this TransportManager
+ * instance is yet to start establishing the connectivity with the
+ * associated Jitsi Videobridge (in case it is being employed at all).
+ */
+ boolean startConnectivityEstablishmentWithJitsiVideobridge = false;
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link CallPeer} whose traffic we will be taking
+ * care of.
+ */
+ protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer)
+ {
+ super(callPeer);
+ }
+
+ /**
+ * Returns the InetAddress that is most likely to be to be used
+ * as a next hop when contacting the specified destination. This is
+ * an utility method that is used whenever we have to choose one of our
+ * local addresses to put in the Via, Contact or (in the case of no
+ * registrar accounts) From headers.
+ *
+ * @param peer the CallPeer that we would contact.
+ *
+ * @return the InetAddress that is most likely to be to be used
+ * as a next hop when contacting the specified destination.
+ *
+ * @throws IllegalArgumentException if destination is not a valid
+ * host/IP/FQDN
+ */
+ @Override
+ protected InetAddress getIntendedDestination(CallPeerJabberImpl peer)
+ {
+ return peer.getProtocolProvider().getNextHop();
+ }
+
+ /**
+ * Returns the ID that we will be assigning to the next candidate we create.
+ *
+ * @return the next ID to use with a candidate.
+ */
+ protected String getNextID()
+ {
+ int nextID;
+
+ synchronized (TransportManagerJabberImpl.class)
+ {
+ nextID = TransportManagerJabberImpl.nextID++;
+ }
+ return Integer.toString(nextID);
+ }
+
+ /**
+ * Gets the MediaStreamTarget to be used as the target of
+ * the MediaStream with a specific MediaType.
+ *
+ * @param mediaType the MediaType of the MediaStream which
+ * is to have its target set to the returned
+ * MediaStreamTarget
+ * @return the MediaStreamTarget to be used as the target
+ * of the MediaStream with the specified MediaType
+ */
+ public abstract MediaStreamTarget getStreamTarget(MediaType mediaType);
+
+ /**
+ * Gets the XML namespace of the Jingle transport implemented by this
+ * TransportManagerJabberImpl.
+ *
+ * @return the XML namespace of the Jingle transport implemented by this
+ * TransportManagerJabberImpl
+ */
+ public abstract String getXmlNamespace();
+
+ /**
+ * Returns the generation that our current candidates belong to.
+ *
+ * @return the generation that we should assign to candidates that we are
+ * currently advertising.
+ */
+ protected int getCurrentGeneration()
+ {
+ return currentGeneration;
+ }
+
+ /**
+ * Increments the generation that we are assigning candidates.
+ */
+ protected void incrementGeneration()
+ {
+ currentGeneration++;
+ }
+
+ /**
+ * Sends transport-related information received from the remote peer to the
+ * associated Jiitsi Videobridge in order to update the (remote)
+ * ColibriConferenceIQ.Channel associated with this
+ * TransportManager instance.
+ *
+ * @param map a Map of media-IceUdpTransportPacketExtension pairs
+ * which represents the transport-related information which has been
+ * received from the remote peer and which is to be sent to the associated
+ * Jitsi Videobridge
+ */
+ protected void sendTransportInfoToJitsiVideobridge(
+ Map map)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ boolean initiator = !peer.isInitiator();
+ ColibriConferenceIQ conferenceRequest = null;
+
+ for (Map.Entry e
+ : map.entrySet())
+ {
+ String media = e.getKey();
+ MediaType mediaType = MediaType.parseString(media);
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, false /* remote */);
+
+ if (channel != null)
+ {
+ IceUdpTransportPacketExtension transport;
+
+ try
+ {
+ transport = cloneTransportAndCandidates(e.getValue());
+ }
+ catch (OperationFailedException ofe)
+ {
+ transport = null;
+ }
+ if (transport == null)
+ continue;
+
+ ColibriConferenceIQ.Channel channelRequest
+ = new ColibriConferenceIQ.Channel();
+
+ channelRequest.setID(channel.getID());
+ channelRequest.setInitiator(initiator);
+ channelRequest.setTransport(transport);
+
+ if (conferenceRequest == null)
+ {
+ if (colibri == null)
+ break;
+ else
+ {
+ String id = colibri.getID();
+
+ if ((id == null) || (id.length() == 0))
+ break;
+ else
+ {
+ conferenceRequest = new ColibriConferenceIQ();
+ conferenceRequest.setID(id);
+ conferenceRequest.setTo(colibri.getFrom());
+ conferenceRequest.setType(IQ.Type.SET);
+ }
+ }
+ }
+ conferenceRequest.getOrCreateContent(media).addChannel(
+ channelRequest);
+ }
+ }
+ if (conferenceRequest != null)
+ {
+ peer.getProtocolProvider().getConnection().sendPacket(
+ conferenceRequest);
+ }
+ }
+
+ /**
+ * Starts transport candidate harvest for a specific
+ * ContentPacketExtension that we are going to offer or answer
+ * with.
+ *
+ * @param theirContent the ContentPacketExtension offered by the
+ * remote peer to which we are going to answer with ourContent or
+ * null if ourContent will be an offer to the remote peer
+ * @param ourContent the ContentPacketExtension for which transport
+ * candidate harvest is to be started
+ * @param transportInfoSender a TransportInfoSender if the
+ * harvested transport candidates are to be sent in a
+ * transport-info rather than in ourContent; otherwise,
+ * null
+ * @param media the media of the RtpDescriptionPacketExtension
+ * child of ourContent
+ * @return a PacketExtension to be added as a child to
+ * ourContent; otherwise, null
+ * @throws OperationFailedException if anything goes wrong while starting
+ * transport candidate harvest for the specified ourContent
+ */
+ protected abstract PacketExtension startCandidateHarvest(
+ ContentPacketExtension theirContent,
+ ContentPacketExtension ourContent,
+ TransportInfoSender transportInfoSender,
+ String media)
+ throws OperationFailedException;
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread. Candidate
+ * harvest would then need to be concluded in the
+ * {@link #wrapupCandidateHarvest()} method which would be called once we
+ * absolutely need the candidates.
+ *
+ * @param theirOffer a media description offer that we've received from the
+ * remote party and that we should use in case we need to know what
+ * transports our peer is using.
+ * @param ourAnswer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param transportInfoSender the TransportInfoSender to be used by
+ * this TransportManagerJabberImpl to send transport-info
+ * JingleIQs from the local peer to the remote peer if this
+ * TransportManagerJabberImpl wishes to utilize
+ * transport-info. Local candidate addresses sent by this
+ * TransportManagerJabberImpl in transport-info are
+ * expected to not be included in the result of
+ * {@link #wrapupCandidateHarvest()}.
+ *
+ * @throws OperationFailedException if we fail to allocate a port number.
+ */
+ public void startCandidateHarvest(
+ List theirOffer,
+ List ourAnswer,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ CallJabberImpl call = peer.getCall();
+ boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge();
+ List cpes
+ = (theirOffer == null) ? ourAnswer : theirOffer;
+
+ /*
+ * If Jitsi Videobridge is to be used, determine which channels are to
+ * be allocated and attempt to allocate them now.
+ */
+ if (isJitsiVideobridge)
+ {
+ Map contentMap
+ = new LinkedHashMap
+ ();
+
+ for (ContentPacketExtension cpe : cpes)
+ {
+ RtpDescriptionPacketExtension rdpe
+ = cpe.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+ MediaType mediaType = MediaType.parseString(rdpe.getMedia());
+
+ /*
+ * The existence of a content for the mediaType and regardless
+ * of the existence of channels in it signals that a channel
+ * allocation request has already been sent for that mediaType.
+ */
+ if ((colibri == null)
+ || (colibri.getContent(mediaType.toString()) == null))
+ {
+ ContentPacketExtension local, remote;
+
+ if (cpes == ourAnswer)
+ {
+ local = cpe;
+ remote
+ = (theirOffer == null)
+ ? null
+ : findContentByName(theirOffer, cpe.getName());
+ }
+ else
+ {
+ local = findContentByName(ourAnswer, cpe.getName());
+ remote = cpe;
+ }
+ contentMap.put(local, remote);
+ }
+ }
+ if (!contentMap.isEmpty())
+ {
+ /*
+ * We are about to request the channel allocations for the media
+ * types found in contentMap. Regardless of the response, we do
+ * not want to repeat these requests.
+ */
+ if (colibri == null)
+ colibri = new ColibriConferenceIQ();
+ for (Map.Entry e
+ : contentMap.entrySet())
+ {
+ ContentPacketExtension cpe = e.getValue();
+
+ if (cpe == null)
+ cpe = e.getKey();
+
+ RtpDescriptionPacketExtension rdpe
+ = cpe.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+
+ colibri.getOrCreateContent(rdpe.getMedia());
+ }
+
+ ColibriConferenceIQ conferenceResult
+ = call.createColibriChannels(peer, contentMap);
+
+ if (conferenceResult != null)
+ {
+ String videobridgeID = colibri.getID();
+ String conferenceResultID = conferenceResult.getID();
+
+ if (videobridgeID == null)
+ colibri.setID(conferenceResultID);
+ else if (!videobridgeID.equals(conferenceResultID))
+ throw new IllegalStateException("conference.id");
+
+ String videobridgeFrom = conferenceResult.getFrom();
+
+ if ((videobridgeFrom != null)
+ && (videobridgeFrom.length() != 0))
+ {
+ colibri.setFrom(videobridgeFrom);
+ }
+
+ for (ColibriConferenceIQ.Content contentResult
+ : conferenceResult.getContents())
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getOrCreateContent(
+ contentResult.getName());
+
+ for (ColibriConferenceIQ.Channel channelResult
+ : contentResult.getChannels())
+ {
+ if (content.getChannel(channelResult.getID())
+ == null)
+ {
+ content.addChannel(channelResult);
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * The call fails if the createColibriChannels method fails
+ * which may happen if the conference packet times out or it
+ * can't be built.
+ */
+ ProtocolProviderServiceJabberImpl
+ .throwOperationFailedException(
+ "Failed to allocate colibri channel.",
+ OperationFailedException.GENERAL_ERROR,
+ null,
+ logger);
+ }
+ }
+ }
+
+ for (ContentPacketExtension cpe : cpes)
+ {
+ String contentName = cpe.getName();
+ ContentPacketExtension ourContent
+ = findContentByName(ourAnswer, contentName);
+
+ //it might be that we decided not to reply to this content
+ if (ourContent != null)
+ {
+ ContentPacketExtension theirContent
+ = (theirOffer == null)
+ ? null
+ : findContentByName(theirOffer, contentName);
+ RtpDescriptionPacketExtension rtpDesc
+ = ourContent.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+ String media = rtpDesc.getMedia();
+ PacketExtension pe
+ = startCandidateHarvest(
+ theirContent,
+ ourContent,
+ transportInfoSender,
+ media);
+
+ if (pe != null)
+ ourContent.addChildExtension(pe);
+ }
+ }
+ }
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread. Candidate
+ * harvest would then need to be concluded in the
+ * {@link #wrapupCandidateHarvest()} method which would be called once we
+ * absolutely need the candidates.
+ *
+ * @param ourOffer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param transportInfoSender the TransportInfoSender to be used by
+ * this TransportManagerJabberImpl to send transport-info
+ * JingleIQs from the local peer to the remote peer if this
+ * TransportManagerJabberImpl wishes to utilize
+ * transport-info. Local candidate addresses sent by this
+ * TransportManagerJabberImpl in transport-info are
+ * expected to not be included in the result of
+ * {@link #wrapupCandidateHarvest()}.
+ * @throws OperationFailedException if we fail to allocate a port number.
+ */
+ public void startCandidateHarvest(
+ List ourOffer,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ startCandidateHarvest(
+ /* theirOffer */ null,
+ ourOffer,
+ transportInfoSender);
+ }
+
+ /**
+ * Notifies the transport manager that it should conclude candidate
+ * harvesting as soon as possible and return the lists of candidates
+ * gathered so far.
+ *
+ * @return the content list that we received earlier (possibly cloned into
+ * a new instance) and that we have updated with transport lists.
+ */
+ public abstract List wrapupCandidateHarvest();
+
+ /**
+ * Looks through the cpExtList and returns the {@link
+ * ContentPacketExtension} with the specified name.
+ *
+ * @param cpExtList the list that we will be searching for a specific
+ * content.
+ * @param name the name of the content element we are looking for.
+ * @return the {@link ContentPacketExtension} with the specified name or
+ * null if no such content element exists.
+ */
+ public static ContentPacketExtension findContentByName(
+ Iterable cpExtList,
+ String name)
+ {
+ for(ContentPacketExtension cpExt : cpExtList)
+ {
+ if(cpExt.getName().equals(name))
+ return cpExt;
+ }
+ return null;
+ }
+
+ /**
+ * Starts the connectivity establishment of this
+ * TransportManagerJabberImpl i.e. checks the connectivity between
+ * the local and the remote peers given the remote counterpart of the
+ * negotiation between them.
+ *
+ * @param remote the collection of ContentPacketExtensions which
+ * represents the remote counterpart of the negotiation between the local
+ * and the remote peer
+ * @return true if connectivity establishment has been started in
+ * response to the call; otherwise, false.
+ * TransportManagerJabberImpl implementations which do not perform
+ * connectivity checks (e.g. raw UDP) should return true. The
+ * default implementation does not perform connectivity checks and always
+ * returns true.
+ */
+ public boolean startConnectivityEstablishment(
+ Iterable remote)
+ {
+ return true;
+ }
+
+ /**
+ * Starts the connectivity establishment of this
+ * TransportManagerJabberImpl i.e. checks the connectivity between
+ * the local and the remote peers given the remote counterpart of the
+ * negotiation between them.
+ *
+ * @param remote a Map of
+ * media-IceUdpTransportPacketExtension pairs which represents the
+ * remote counterpart of the negotiation between the local and the remote
+ * peers
+ * @return true if connectivity establishment has been started in
+ * response to the call; otherwise, false.
+ * TransportManagerJabberImpl implementations which do not perform
+ * connectivity checks (e.g. raw UDP) should return true. The
+ * default implementation does not perform connectivity checks and always
+ * returns true.
+ */
+ protected boolean startConnectivityEstablishment(
+ Map remote)
+ {
+ return true;
+ }
+
+ /**
+ * Notifies this TransportManagerJabberImpl that it should conclude
+ * any started connectivity establishment.
+ *
+ * @throws OperationFailedException if anything goes wrong with connectivity
+ * establishment (i.e. ICE failed, ...)
+ */
+ public void wrapupConnectivityEstablishment()
+ throws OperationFailedException
+ {
+ }
+
+ /**
+ * Removes a content with a specific name from the transport-related part of
+ * the session represented by this TransportManagerJabberImpl which
+ * may have been reported through previous calls to the
+ * startCandidateHarvest and
+ * startConnectivityEstablishment methods.
+ *
+ * Note: Because TransportManager deals with
+ * MediaTypes, not content names and
+ * TransportManagerJabberImpl does not implement translating from
+ * content name to MediaType, implementers are expected to call
+ * {@link TransportManager#closeStreamConnector(MediaType)}.
+ *
+ *
+ * @param name the name of the content to be removed from the
+ * transport-related part of the session represented by this
+ * TransportManagerJabberImpl
+ */
+ public abstract void removeContent(String name);
+
+ /**
+ * Removes a content with a specific name from a specific collection of
+ * contents and closes any associated StreamConnector.
+ *
+ * @param contents the collection of contents to remove the content with the
+ * specified name from
+ * @param name the name of the content to remove
+ * @return the removed ContentPacketExtension if any; otherwise,
+ * null
+ */
+ protected ContentPacketExtension removeContent(
+ Iterable contents,
+ String name)
+ {
+ for (Iterator contentIter = contents.iterator();
+ contentIter.hasNext();)
+ {
+ ContentPacketExtension content = contentIter.next();
+
+ if (name.equals(content.getName()))
+ {
+ contentIter.remove();
+
+ // closeStreamConnector
+ RtpDescriptionPacketExtension rtpDescription
+ = content.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+
+ if (rtpDescription != null)
+ {
+ closeStreamConnector(
+ MediaType.parseString(rtpDescription.getMedia()));
+ }
+
+ return content;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Clones the attributes, namespace and text of a specific
+ * AbstractPacketExtension into a new
+ * AbstractPacketExtension instance of the same run-time type.
+ *
+ * @param src the AbstractPacketExtension to be cloned
+ * @return a new AbstractPacketExtension instance of the run-time
+ * type of the specified src which has the same attributes,
+ * namespace and text
+ * @throws Exception if an error occurs during the cloning of the specified
+ * src
+ */
+ private static T clone(T src)
+ throws Exception
+ {
+ @SuppressWarnings("unchecked")
+ T dst = (T) src.getClass().newInstance();
+
+ // attributes
+ for (String name : src.getAttributeNames())
+ dst.setAttribute(name, src.getAttribute(name));
+ // namespace
+ dst.setNamespace(src.getNamespace());
+ // text
+ dst.setText(src.getText());
+ return dst;
+ }
+
+ /**
+ * Clones a specific IceUdpTransportPacketExtension and its
+ * candidates.
+ *
+ * @param src the IceUdpTransportPacketExtension to be cloned
+ * @return a new IceUdpTransportPacketExtension instance which has
+ * the same run-time type, attributes, namespace, text and candidates as the
+ * specified src
+ * @throws OperationFailedException if an error occurs during the cloing of
+ * the specified src and its candidates
+ */
+ static IceUdpTransportPacketExtension cloneTransportAndCandidates(
+ IceUdpTransportPacketExtension src)
+ throws OperationFailedException
+ {
+ IceUdpTransportPacketExtension dst = null;
+
+ if (src != null)
+ {
+ try
+ {
+ dst = clone(src);
+
+ for (CandidatePacketExtension srcCand : src.getCandidateList())
+ {
+ if (!(srcCand instanceof RemoteCandidatePacketExtension))
+ dst.addCandidate(clone(srcCand));
+ }
+ }
+ catch (Exception e)
+ {
+ dst = null;
+ ProtocolProviderServiceJabberImpl
+ .throwOperationFailedException(
+ "Failed to close transport and candidates.",
+ OperationFailedException.GENERAL_ERROR,
+ e,
+ logger);
+ }
+ }
+ return dst;
+ }
+
+ /**
+ * Releases the resources acquired by this TransportManager and
+ * prepares it for garbage collection.
+ */
+ public void close()
+ {
+ for (MediaType mediaType : MediaType.values())
+ closeStreamConnector(mediaType);
+ }
+
+ /**
+ * Closes a specific StreamConnector associated with a specific
+ * MediaType. If this TransportManager has a reference to
+ * the specified streamConnector, it remains.
+ * Also expires the ColibriConferenceIQ.Channel associated with
+ * the closed StreamConnector.
+ *
+ * @param mediaType the MediaType associated with the specified
+ * streamConnector
+ * @param streamConnector the StreamConnector to be closed
+ */
+ @Override
+ protected void closeStreamConnector(
+ MediaType mediaType,
+ StreamConnector streamConnector)
+ {
+ try
+ {
+ boolean superCloseStreamConnector = true;
+
+ if (streamConnector instanceof ColibriStreamConnector)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+
+ if (peer != null)
+ {
+ CallJabberImpl call = peer.getCall();
+
+ if (call != null)
+ {
+ superCloseStreamConnector = false;
+ call.closeColibriStreamConnector(
+ peer,
+ mediaType,
+ (ColibriStreamConnector) streamConnector);
+ }
+ }
+ }
+ if (superCloseStreamConnector)
+ super.closeStreamConnector(mediaType, streamConnector);
+ }
+ finally
+ {
+ /*
+ * Expire the ColibriConferenceIQ.Channel associated with the closed
+ * StreamConnector.
+ */
+ if (colibri != null)
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getContent(mediaType.toString());
+
+ if (content != null)
+ {
+ List channels
+ = content.getChannels();
+
+ if (channels.size() == 2)
+ {
+ ColibriConferenceIQ requestConferenceIQ
+ = new ColibriConferenceIQ();
+
+ requestConferenceIQ.setID(colibri.getID());
+
+ ColibriConferenceIQ.Content requestContent
+ = requestConferenceIQ.getOrCreateContent(
+ content.getName());
+
+ requestContent.addChannel(channels.get(1 /* remote */));
+
+ /*
+ * Regardless of whether the request to expire the
+ * Channel associated with mediaType succeeds, consider
+ * the Channel in question expired. Since
+ * RawUdpTransportManager allocates a single channel per
+ * MediaType, consider the whole Content expired.
+ */
+ colibri.removeContent(content);
+
+ CallPeerJabberImpl peer = getCallPeer();
+
+ if (peer != null)
+ {
+ CallJabberImpl call = peer.getCall();
+
+ if (call != null)
+ {
+ call.expireColibriChannels(
+ peer,
+ requestConferenceIQ);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Adds support for telephony conferences utilizing the Jitsi Videobridge
+ * server-side technology.
+ *
+ * @see #doCreateStreamConnector(MediaType)
+ */
+ @Override
+ protected StreamConnector createStreamConnector(final MediaType mediaType)
+ throws OperationFailedException
+ {
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, true /* local */);
+
+ if (channel != null)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ CallJabberImpl call = peer.getCall();
+ StreamConnector streamConnector
+ = call.createColibriStreamConnector(
+ peer,
+ mediaType,
+ channel,
+ new StreamConnectorFactory()
+ {
+ public StreamConnector createStreamConnector()
+ {
+ try
+ {
+ return doCreateStreamConnector(mediaType);
+ }
+ catch (OperationFailedException ofe)
+ {
+ return null;
+ }
+ }
+ });
+
+ if (streamConnector != null)
+ return streamConnector;
+ }
+
+ return doCreateStreamConnector(mediaType);
+ }
+
+ protected abstract PacketExtension createTransport(String media)
+ throws OperationFailedException;
+
+ protected PacketExtension createTransportForStartCandidateHarvest(
+ String media)
+ throws OperationFailedException
+ {
+ PacketExtension pe = null;
+
+ if (getCallPeer().isJitsiVideobridge())
+ {
+ MediaType mediaType = MediaType.parseString(media);
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, false /* remote */);
+
+ if (channel != null)
+ pe = cloneTransportAndCandidates(channel.getTransport());
+ }
+ else
+ pe = createTransport(media);
+ return pe;
+ }
+
+ /**
+ * Initializes a new PacketExtension instance appropriate to the
+ * type of Jingle transport represented by this TransportManager.
+ * The new instance is not initialized with any attributes or child
+ * extensions.
+ *
+ * @return a new PacketExtension instance appropriate to the type
+ * of Jingle transport represented by this TransportManager
+ */
+ protected abstract PacketExtension createTransportPacketExtension();
+
+ /**
+ * Creates a media StreamConnector for a stream of a specific
+ * MediaType. The minimum and maximum of the media port boundaries
+ * are taken into account.
+ *
+ * @param mediaType the MediaType of the stream for which a
+ * StreamConnector is to be created
+ * @return a StreamConnector for the stream of the specified
+ * mediaType
+ * @throws OperationFailedException if the binding of the sockets fails
+ */
+ protected StreamConnector doCreateStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ return super.createStreamConnector(mediaType);
+ }
+
+ /**
+ * Finds a TransportManagerJabberImpl participating in a telephony
+ * conference utilizing the Jitsi Videobridge server-side technology that
+ * this instance is participating in which is establishing the connectivity
+ * with the Jitsi Videobridge server (as opposed to a CallPeer).
+ *
+ * @return a TransportManagerJabberImpl which is participating in
+ * a telephony conference utilizing the Jitsi Videobridge server-side
+ * technology that this instance is participating in which is establishing
+ * the connectivity with the Jitsi Videobridge server (as opposed to a
+ * CallPeer).
+ */
+ TransportManagerJabberImpl
+ findTransportManagerEstablishingConnectivityWithJitsiVideobridge()
+ {
+ Call call = getCallPeer().getCall();
+ TransportManagerJabberImpl transportManager = null;
+
+ if (call != null)
+ {
+ CallConference conference = call.getConference();
+
+ if ((conference != null) && conference.isJitsiVideobridge())
+ {
+ for (Call aCall : conference.getCalls())
+ {
+ Iterator extends CallPeer> callPeerIter
+ = aCall.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ CallPeer aCallPeer = callPeerIter.next();
+
+ if (aCallPeer instanceof CallPeerJabberImpl)
+ {
+ TransportManagerJabberImpl aTransportManager
+ = ((CallPeerJabberImpl) aCallPeer)
+ .getMediaHandler()
+ .getTransportManager();
+
+ if (aTransportManager
+ .isEstablishingConnectivityWithJitsiVideobridge)
+ {
+ transportManager = aTransportManager;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return transportManager;
+ }
+
+ /**
+ * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content
+ * associated with a specific MediaType and is to be either locally
+ * or remotely used.
+ *
+ * Note: Modifications to the ColibriConferenceIQ.Channel
+ * instance returned by the method propagate to (the state of) this
+ * instance.
+ *
+ *
+ * @param mediaType the MediaType associated with the content which
+ * contains the ColibriConferenceIQ.Channel to get
+ * @param local true if the ColibriConferenceIQ.Channel
+ * which is to be used locally is to be returned or false for the
+ * one which is to be used remotely
+ * @return the ColibriConferenceIQ.Channel which belongs to a
+ * content associated with the specified mediaType and which is to
+ * be used in accord with the specified local indicator if such a
+ * channel exists; otherwise, null
+ */
+ ColibriConferenceIQ.Channel getColibriChannel(
+ MediaType mediaType,
+ boolean local)
+ {
+ ColibriConferenceIQ.Channel channel = null;
+
+ if (colibri != null)
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getContent(mediaType.toString());
+
+ if (content != null)
+ {
+ List channels
+ = content.getChannels();
+
+ if (channels.size() == 2)
+ channel = channels.get(local ? 0 : 1);
+ }
+ }
+ return channel;
+ }
+}