/*
* Jicofo, the Jitsi Conference Focus.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.protocol.xmpp;
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.impl.protocol.jabber.extensions.jitsimeet.*;
import net.java.sip.communicator.util.*;
import org.jitsi.jicofo.*;
import org.jitsi.protocol.xmpp.util.*;
import org.jivesoftware.smack.packet.*;
import java.util.*;
/**
* Class provides template implementation of {@link OperationSetJingle}.
*
* @author Pawel Domas
*/
public abstract class AbstractOperationSetJingle
implements OperationSetJingle
{
/**
* The logger.
*/
private static final Logger logger
= Logger.getLogger(AbstractOperationSetJingle.class);
/**
* The list of active Jingle session.
*/
protected Map<String, JingleSession> sessions
= new HashMap<String, JingleSession>();
protected JingleRequestHandler requestHandler;
/**
* Implementing classes should return our JID here.
*/
protected abstract String getOurJID();
/**
* Returns {@link XmppConnection} implementation.
*/
protected abstract XmppConnection getConnection();
/**
* Finds Jingle session for given session identifier.
*
* @param sid the identifier of the session for which we're looking for.
*
* @return Jingle session for given session identifier or <tt>null</tt>
* if no such session exists.
*/
protected JingleSession getSession(String sid)
{
return sessions.get(sid);
}
/**
* Sets the {@link JingleRequestHandler} that will be associated with this
* instance.
*
* @param jingleHandler {@link JingleRequestHandler} object that will be
* receiving notifications from this instance.
*/
@Override
public void setRequestHandler(JingleRequestHandler jingleHandler)
{
this.requestHandler = jingleHandler;
}
/**
* Sends 'session-initiate' to the peer identified by given <tt>address</tt>
*
* @param useBundle <tt>true</tt> if invite IQ should include
* {@link GroupPacketExtension}
* @param address the XMPP address where 'session-initiate' will be sent.
* @param contents the list of <tt>ContentPacketExtension</tt> describing
* media offer.
*/
@Override
public void initiateSession(boolean useBundle,
String address,
List<ContentPacketExtension> contents)
{
logger.info("INVITE PEER: " + address);
String sid = JingleIQ.generateSID();
JingleSession session = new JingleSession(sid, address);
sessions.put(sid, session);
JingleIQ inviteIQ
= JinglePacketFactory.createSessionInitiate(
getOurJID(),
address,
sid,
contents);
if (useBundle)
{
GroupPacketExtension group
= GroupPacketExtension.createBundleGroup(contents);
inviteIQ.addExtension(group);
for (ContentPacketExtension content : contents)
{
// FIXME: is it mandatory ?
// http://estos.de/ns/bundle
content.addChildExtension(
new BundlePacketExtension());
}
}
getConnection().sendPacket(inviteIQ);
}
/**
* The logic for processing received JingleIQs.
*
* @param iq the <tt>JingleIQ</tt> to process.
*/
protected void processJingleIQ(JingleIQ iq)
{
JingleSession session = getSession(iq.getSID());
JingleAction action = iq.getAction();
if (session == null)
{
logger.error(
"Action: " + action
+ ", no session found for SID " + iq.getSID());
return;
}
if (requestHandler == null)
{
logger.error("No request handler set.");
return;
}
if (JingleAction.SESSION_ACCEPT.equals(action))
{
requestHandler.onSessionAccept(
session, iq.getContentList());
}
else if (JingleAction.TRANSPORT_INFO.equals(action))
{
requestHandler.onTransportInfo(
session, iq.getContentList());
}
else if (JingleAction.ADDSOURCE.equals(action)
|| JingleAction.SOURCEADD.equals(action))
{
requestHandler.onAddSource(session, iq.getContentList());
}
else if (JingleAction.REMOVESOURCE.equals(action)
|| JingleAction.SOURCEREMOVE.equals(action))
{
requestHandler.onRemoveSource(session, iq.getContentList());
}
else
{
logger.warn("unsupported action " + action);
}
}
/**
* Sends 'source-add' notification to the peer of given
* <tt>JingleSession</tt>.
*
* @param ssrcs the map of media SSRCs that will be included in
* the notification.
* @param ssrcGroupMap the map of media SSRC groups that will be included in
* the notification.
* @param session the <tt>JingleSession</tt> used to send the notification.
*/
@Override
public void sendAddSourceIQ(MediaSSRCMap ssrcs,
MediaSSRCGroupMap ssrcGroupMap,
JingleSession session)
{
JingleIQ addSourceIq = new JingleIQ();
addSourceIq.setAction(JingleAction.SOURCEADD);
addSourceIq.setFrom(getOurJID());
addSourceIq.setType(IQ.Type.SET);
for (String media : ssrcs.getMediaTypes())
{
ContentPacketExtension content
= new ContentPacketExtension();
content.setName(media);
RtpDescriptionPacketExtension rtpDesc
= new RtpDescriptionPacketExtension();
rtpDesc.setMedia(media);
content.addChildExtension(rtpDesc);
for (SourcePacketExtension ssrc : ssrcs.getSSRCsForMedia(media))
{
try
{
rtpDesc.addChildExtension(
ssrc.copy());
}
catch (Exception e)
{
logger.error("Copy SSRC error", e);
}
}
addSourceIq.addContent(content);
}
if (ssrcGroupMap != null)
{
for (String media : ssrcGroupMap.getMediaTypes())
{
ContentPacketExtension content
= addSourceIq.getContentByName(media);
RtpDescriptionPacketExtension rtpDesc;
if (content == null)
{
// It means content was not created when adding SSRCs...
logger.warn(
"No SSRCs to be added when group exists for media: "
+ media);
content = new ContentPacketExtension();
content.setName(media);
addSourceIq.addContent(content);
rtpDesc = new RtpDescriptionPacketExtension();
rtpDesc.setMedia(media);
content.addChildExtension(rtpDesc);
}
else
{
rtpDesc = content.getFirstChildOfType(
RtpDescriptionPacketExtension.class);
}
for (SSRCGroup ssrcGroup
: ssrcGroupMap.getSSRCGroupsForMedia(media))
{
try
{
rtpDesc.addChildExtension(ssrcGroup.getExtensionCopy());
}
catch (Exception e)
{
logger.error("Copy SSRC GROUP error", e);
}
}
}
}
String peerSid = session.getSessionID();
addSourceIq.setTo(session.getAddress());
addSourceIq.setSID(peerSid);
logger.info("Notify add SSRC" + session.getAddress()
+ " SID: " + peerSid);
getConnection().sendPacket(addSourceIq);
}
/**
* Sends 'source-remove' notification to the peer of given
* <tt>JingleSession</tt>.
*
* @param ssrcs the map of media SSRCs that will be included in
* the notification.
* @param ssrcGroupMap the map of media SSRC groups that will be included in
* the notification.
* @param session the <tt>JingleSession</tt> used to send the notification.
*/
@Override
public void sendRemoveSourceIQ(MediaSSRCMap ssrcs,
MediaSSRCGroupMap ssrcGroupMap,
JingleSession session)
{
JingleIQ removeSourceIq = new JingleIQ();
removeSourceIq.setAction(JingleAction.SOURCEREMOVE);
removeSourceIq.setFrom(getOurJID());
removeSourceIq.setType(IQ.Type.SET);
for (String media : ssrcs.getMediaTypes())
{
ContentPacketExtension content
= new ContentPacketExtension();
content.setName(media);
RtpDescriptionPacketExtension rtpDesc
= new RtpDescriptionPacketExtension();
rtpDesc.setMedia(media);
content.addChildExtension(rtpDesc);
for (SourcePacketExtension ssrc : ssrcs.getSSRCsForMedia(media))
{
try
{
rtpDesc.addChildExtension(
ssrc.copy());
}
catch (Exception e)
{
logger.error("Copy SSRC error", e);
}
}
removeSourceIq.addContent(content);
}
if (ssrcGroupMap != null)
{
for (String media : ssrcGroupMap.getMediaTypes())
{
ContentPacketExtension content
= removeSourceIq.getContentByName(media);
RtpDescriptionPacketExtension rtpDesc;
if (content == null)
{
// It means content was not created when adding SSRCs...
logger.warn(
"No SSRCs to be removed when group exists for media: "
+ media);
content = new ContentPacketExtension();
content.setName(media);
removeSourceIq.addContent(content);
rtpDesc = new RtpDescriptionPacketExtension();
rtpDesc.setMedia(media);
content.addChildExtension(rtpDesc);
}
else
{
rtpDesc = content.getFirstChildOfType(
RtpDescriptionPacketExtension.class);
}
for (SSRCGroup ssrcGroup
: ssrcGroupMap.getSSRCGroupsForMedia(media))
{
try
{
rtpDesc.addChildExtension(ssrcGroup.getExtensionCopy());
}
catch (Exception e)
{
logger.error("Copy SSRC GROUP error", e);
}
}
}
}
String peerSid = session.getSessionID();
removeSourceIq.setTo(session.getAddress());
removeSourceIq.setSID(peerSid);
logger.info("Notify remove SSRC " + session.getAddress()
+ " SID: " + peerSid);
XmppConnection connection = getConnection();
connection.sendPacket(removeSourceIq);
}
/**
* Terminates given Jingle session by sending 'session-terminate' with some
* {@link Reason} if provided.
*
* @param session the <tt>JingleSession</tt> to terminate.
* @param reason one of {@link Reason} enum that indicates why the session
* is being ended or <tt>null</tt> to omit.
*/
@Override
public void terminateSession(JingleSession session, Reason reason)
{
logger.info("Terminate session: " + session.getAddress());
JingleIQ terminate
= JinglePacketFactory.createSessionTerminate(
getOurJID(),
session.getAddress(),
session.getSessionID(),
reason, null);
getConnection().sendPacket(terminate);
sessions.remove(session.getSessionID());
}
}