1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-23 14:52:06 +01:00
Smack/jingle/extension/source/org/jivesoftware/smackx/jingle/JingleSession.java

1249 lines
41 KiB
Java

/**
* $RCSfile: JingleSession.java,v $
* $Revision: 1.20 $
* $Date: 2007/07/18 18:29:21 $
*
* Copyright (C) 2002-2006 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smackx.jingle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
import org.jivesoftware.smackx.jingle.media.MediaReceivedListener;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleError;
/**
* An abstract Jingle session. <p/> This class contains some basic properties of
* every Jingle session. However, the concrete implementation can be found in
* subclasses.
*
* @author Alvaro Saurin
* @author Jeff Williams
*/
public class JingleSession extends JingleNegotiator implements MediaReceivedListener {
private static final SmackLogger LOGGER = SmackLogger.getLogger(JingleSession.class);
// static
private static final HashMap<Connection, JingleSession> sessions = new HashMap<Connection, JingleSession>();
private static final Random randomGenerator = new Random();
// non-static
private String initiator; // Who started the communication
private String responder; // The other endpoint
private String sid; // A unique id that identifies this session
ConnectionListener connectionListener;
PacketListener packetListener;
PacketFilter packetFilter;
protected List<JingleMediaManager> jingleMediaManagers = null;
private JingleSessionState sessionState;
private List<ContentNegotiator> contentNegotiators;
private Connection connection;
private String sessionInitPacketID;
private Map<String, JingleMediaSession> mediaSessionMap;
/**
* Full featured JingleSession constructor
*
* @param conn
* the Connection which is used
* @param initiator
* the initiator JID
* @param responder
* the responder JID
* @param sessionid
* the session ID
* @param jingleMediaManager
* the jingleMediaManager
*/
public JingleSession(Connection conn, String initiator, String responder, String sessionid,
List<JingleMediaManager> jingleMediaManagers) {
super();
this.initiator = initiator;
this.responder = responder;
this.sid = sessionid;
this.jingleMediaManagers = jingleMediaManagers;
this.setSession(this);
this.connection = conn;
// Initially, we don't known the session state.
setSessionState(JingleSessionStateUnknown.getInstance());
contentNegotiators = new ArrayList<ContentNegotiator>();
mediaSessionMap = new HashMap<String, JingleMediaSession>();
// Add the session to the list and register the listeneres
registerInstance();
installConnectionListeners(conn);
}
/**
* JingleSession constructor (for an outgoing Jingle session)
*
* @param conn
* Connection
* @param initiator
* the initiator JID
* @param responder
* the responder JID
* @param jingleMediaManager
* the jingleMediaManager
*/
public JingleSession(Connection conn, JingleSessionRequest request, String initiator, String responder,
List<JingleMediaManager> jingleMediaManagers) {
this(conn, initiator, responder, generateSessionId(), jingleMediaManagers);
//sessionRequest = request; // unused
}
/**
* Get the session initiator
*
* @return the initiator
*/
public String getInitiator() {
return initiator;
}
public Connection getConnection() {
return connection;
}
/**
* Set the session initiator
*
* @param initiator
* the initiator to set
*/
public void setInitiator(String initiator) {
this.initiator = initiator;
}
/**
* Get the Media Manager of this Jingle Session
*
* @return
*/
public List<JingleMediaManager> getMediaManagers() {
return jingleMediaManagers;
}
/**
* Set the Media Manager of this Jingle Session
*
* @param jingleMediaManager
*/
public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
this.jingleMediaManagers = jingleMediaManagers;
}
/**
* Get the session responder
*
* @return the responder
*/
public String getResponder() {
return responder;
}
/**
* Set the session responder.
*
* @param responder
* the receptor to set
*/
public void setResponder(String responder) {
this.responder = responder;
}
/**
* Get the session ID
*
* @return the sid
*/
public String getSid() {
return sid;
}
/**
* Set the session ID
*
* @param sessionId
* the sid to set
*/
protected void setSid(String sessionId) {
sid = sessionId;
}
/**
* Generate a unique session ID.
*/
protected static String generateSessionId() {
return String.valueOf(Math.abs(randomGenerator.nextLong()));
}
/**
* Validate the state changes.
*/
public void setSessionState(JingleSessionState stateIs) {
LOGGER.debug("Session state change: " + sessionState + "->" + stateIs);
stateIs.enter();
sessionState = stateIs;
}
public JingleSessionState getSessionState() {
return sessionState;
}
/**
* Return true if all of the media managers have finished
*/
public boolean isFullyEstablished() {
boolean result = true;
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (!contentNegotiator.isFullyEstablished())
result = false;
}
return result;
}
// ----------------------------------------------------------------------------------------------------------
// Receive section
// ----------------------------------------------------------------------------------------------------------
/**
* Process and respond to an incoming packet. <p/> This method is called
* from the packet listener dispatcher when a new packet has arrived. The
* method is responsible for recognizing the packet type and, depending on
* the current state, delivering it to the right event handler and wait for
* a response. The response will be another Jingle packet that will be sent
* to the other end point.
*
* @param iq
* the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
*/
public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException {
List<IQ> responses = new ArrayList<IQ>();
String responseId = null;
LOGGER.debug("Packet: " + iq.toXML());
try {
// Dispatch the packet to the JingleNegotiators and get back a list of the results.
responses.addAll(dispatchIncomingPacket(iq, null));
if (iq != null) {
responseId = iq.getPacketID();
// Send the IQ to each of the content negotiators for further processing.
// Each content negotiator may pass back a list of JingleContent for addition to the response packet.
for (ContentNegotiator contentNegotiator : contentNegotiators) {
// If at this point the content negotiator isn't started, it's because we sent a session-init jingle
// packet from startOutgoing() and we're waiting for the other side to let us know they're ready
// to take jingle packets. (This packet might be a session-terminate, but that will get handled
// later.
if (!contentNegotiator.isStarted()) {
contentNegotiator.start();
}
responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId));
}
}
// Acknowledge the IQ reception
// Not anymore. The state machine generates an appropriate response IQ that
// gets sent back at the end of this routine.
//sendAck(iq);
} catch (JingleException e) {
// Send an error message, if present
JingleError error = e.getError();
if (error != null) {
responses.add(createJingleError(iq, error));
}
// Notify the session end and close everything...
triggerSessionClosedOnError(e);
}
// // If the response is anything other than a RESULT then send it now.
// if ((response != null) && (!response.getType().equals(IQ.Type.RESULT))) {
// getConnection().sendPacket(response);
// }
// Loop through all of the responses and send them.
for (IQ response : responses) {
sendPacket(response);
}
}
/**
* Dispatch an incoming packet. The method is responsible for recognizing
* the packet type and, depending on the current state, delivering the
* packet to the right event handler and wait for a response.
*
* @param iq
* the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
*/
public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException {
List<IQ> responses = new ArrayList<IQ>();
IQ response = null;
if (iq != null) {
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
// TODO getState().eventError(iq);
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
// The other side provisionally accepted our session-initiate.
// Kick off some negotiators.
if (iq.getPacketID().equals(sessionInitPacketID)) {
startNegotiators();
}
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
// It is not an error: it is a Jingle packet...
Jingle jin = (Jingle) iq;
JingleActionEnum action = jin.getAction();
// Depending on the state we're in we'll get different processing actions.
// (See Design Patterns AKA GoF State behavioral pattern.)
response = getSessionState().processJingle(this, jin, action);
}
}
if (response != null) {
// Save the packet id, for recognizing ACKs...
addExpectedId(response.getPacketID());
responses.add(response);
}
return responses;
}
/**
* Add a new content negotiator on behalf of a <content> section received.
*/
public void addContentNegotiator(ContentNegotiator inContentNegotiator) {
contentNegotiators.add(inContentNegotiator);
}
// ----------------------------------------------------------------------------------------------------------
// Send section
// ----------------------------------------------------------------------------------------------------------
public void sendPacket(IQ iq) {
if (iq instanceof Jingle) {
sendFormattedJingle((Jingle) iq);
} else {
getConnection().sendPacket(iq);
}
}
/**
* Complete and send a packet. Complete all the null fields in a Jingle
* reponse, using the session information we have.
*
* @param jout
* the Jingle packet we want to complete and send
*/
public Jingle sendFormattedJingle(Jingle jout) {
return sendFormattedJingle(null, jout);
}
/**
* Complete and send a packet. Complete all the null fields in a Jingle
* reponse, using the session information we have or some info from the
* incoming packet.
*
* @param iq
* The Jingle packet we are responing to
* @param jout
* the Jingle packet we want to complete and send
*/
public Jingle sendFormattedJingle(IQ iq, Jingle jout) {
if (jout != null) {
if (jout.getInitiator() == null) {
jout.setInitiator(getInitiator());
}
if (jout.getResponder() == null) {
jout.setResponder(getResponder());
}
if (jout.getSid() == null) {
jout.setSid(getSid());
}
String me = getConnection().getUser();
String other = getResponder().equals(me) ? getInitiator() : getResponder();
if (jout.getTo() == null) {
if (iq != null) {
jout.setTo(iq.getFrom());
} else {
jout.setTo(other);
}
}
if (jout.getFrom() == null) {
if (iq != null) {
jout.setFrom(iq.getTo());
} else {
jout.setFrom(me);
}
}
// The the packet.
if ((getConnection() != null) && (getConnection().isConnected()))
getConnection().sendPacket(jout);
}
return jout;
}
/**
* @param inJingle
* @param inAction
*/
// private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) {
//
// if (inAction == JingleActionEnum.SESSION_INITIATE) {
// // Prepare to receive and act on response packets.
// updatePacketListener();
//
// // Send the actual packet.
// sendPacket(inJingle);
//
// // Change to the PENDING state.
// setSessionState(JingleSessionStateEnum.PENDING);
// } else {
// throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state.");
// }
// }
/**
* Acknowledge a IQ packet.
*
* @param iq
* The IQ to acknowledge
*/
public IQ createAck(IQ iq) {
IQ result = null;
if (iq != null) {
// Don't acknowledge ACKs, errors...
if (iq.getType().equals(IQ.Type.SET)) {
IQ ack = createIQ(iq.getPacketID(), iq.getFrom(), iq.getTo(), IQ.Type.RESULT);
// No! Don't send it. Let it flow to the normal way IQ results get processed and sent.
// getConnection().sendPacket(ack);
result = ack;
}
}
return result;
}
/**
* Send a content info message.
*/
// public synchronized void sendContentInfo(ContentInfo ci) {
// sendPacket(new Jingle(new JingleContentInfo(ci)));
// }
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return Jingle.getSessionHash(getSid(), getInitiator());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final JingleSession other = (JingleSession) obj;
if (initiator == null) {
if (other.initiator != null) {
return false;
}
} else if (!initiator.equals(other.initiator)) {
// Todo check behavior
// return false;
}
if (responder == null) {
if (other.responder != null) {
return false;
}
} else if (!responder.equals(other.responder)) {
return false;
}
if (sid == null) {
if (other.sid != null) {
return false;
}
} else if (!sid.equals(other.sid)) {
return false;
}
return true;
}
// Instances management
/**
* Clean a session from the list.
*
* @param connection
* The connection to clean up
*/
private void unregisterInstanceFor(Connection connection) {
synchronized (sessions) {
sessions.remove(connection);
}
}
/**
* Register this instance.
*/
private void registerInstance() {
synchronized (sessions) {
sessions.put(getConnection(), this);
}
}
/**
* Returns the JingleSession related to a particular connection.
*
* @param con
* A XMPP connection
* @return a Jingle session
*/
public static JingleSession getInstanceFor(Connection con) {
if (con == null) {
throw new IllegalArgumentException("Connection cannot be null");
}
JingleSession result = null;
synchronized (sessions) {
if (sessions.containsKey(con)) {
result = sessions.get(con);
}
}
return result;
}
/**
* Configure a session, setting some action listeners...
*
* @param connection
* The connection to set up
*/
private void installConnectionListeners(final Connection connection) {
if (connection != null) {
connectionListener = new ConnectionListener() {
public void connectionClosed() {
unregisterInstanceFor(connection);
}
public void connectionClosedOnError(java.lang.Exception e) {
unregisterInstanceFor(connection);
}
public void reconnectingIn(int i) {
}
public void reconnectionSuccessful() {
}
public void reconnectionFailed(Exception exception) {
}
};
connection.addConnectionListener(connectionListener);
}
}
private void removeConnectionListener() {
if (connectionListener != null) {
getConnection().removeConnectionListener(connectionListener);
LOGGER.debug("JINGLE SESSION: REMOVE CONNECTION LISTENER");
}
}
/**
* Remove the packet listener used for processing packet.
*/
protected void removePacketListener() {
if (packetListener != null) {
getConnection().removePacketListener(packetListener);
LOGGER.debug("JINGLE SESSION: REMOVE PACKET LISTENER");
}
}
/**
* Install the packet listener. The listener is responsible for responding
* to any packet that we receive...
*/
protected void updatePacketListener() {
removePacketListener();
LOGGER.debug("UpdatePacketListener");
packetListener = new PacketListener() {
public void processPacket(Packet packet) {
try {
receivePacketAndRespond((IQ) packet);
} catch (XMPPException e) {
e.printStackTrace();
}
}
};
packetFilter = new PacketFilter() {
public boolean accept(Packet packet) {
if (packet instanceof IQ) {
IQ iq = (IQ) packet;
String me = getConnection().getUser();
if (!iq.getTo().equals(me)) {
return false;
}
String other = getResponder().equals(me) ? getInitiator() : getResponder();
if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) {
return false;
}
if (iq instanceof Jingle) {
Jingle jin = (Jingle) iq;
String sid = jin.getSid();
if (sid == null || !sid.equals(getSid())) {
LOGGER.debug("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML());
return false;
}
String ini = jin.getInitiator();
if (!ini.equals(getInitiator())) {
LOGGER.debug("Ignored Jingle(INI): " + iq.toXML());
return false;
}
} else {
// We accept some non-Jingle IQ packets: ERRORs and ACKs
if (iq.getType().equals(IQ.Type.SET)) {
LOGGER.debug("Ignored Jingle(TYPE): " + iq.toXML());
return false;
} else if (iq.getType().equals(IQ.Type.GET)) {
LOGGER.debug("Ignored Jingle(TYPE): " + iq.toXML());
return false;
}
}
return true;
}
return false;
}
};
getConnection().addPacketListener(packetListener, packetFilter);
}
// Listeners
/**
* Add a listener for jmf negotiation events
*
* @param li
* The listener
*/
public void addMediaListener(JingleMediaListener li) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getMediaNegotiator() != null) {
contentNegotiator.getMediaNegotiator().addListener(li);
}
}
}
/**
* Remove a listener for jmf negotiation events
*
* @param li
* The listener
*/
public void removeMediaListener(JingleMediaListener li) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getMediaNegotiator() != null) {
contentNegotiator.getMediaNegotiator().removeListener(li);
}
}
}
/**
* Add a listener for transport negotiation events
*
* @param li
* The listener
*/
public void addTransportListener(JingleTransportListener li) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getTransportNegotiator() != null) {
contentNegotiator.getTransportNegotiator().addListener(li);
}
}
}
/**
* Remove a listener for transport negotiation events
*
* @param li
* The listener
*/
public void removeTransportListener(JingleTransportListener li) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getTransportNegotiator() != null) {
contentNegotiator.getTransportNegotiator().removeListener(li);
}
}
}
/**
* Setup the listeners that act on events coming from the lower level negotiators.
*/
public void setupListeners() {
JingleMediaListener jingleMediaListener = new JingleMediaListener() {
public void mediaClosed(PayloadType cand) {
}
public void mediaEstablished(PayloadType pt) {
if (isFullyEstablished()) {
Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
// Build up a response packet from each media manager.
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
jout.addContent(contentNegotiator.getJingleContent());
}
// Send the "accept" and wait for the ACK
addExpectedId(jout.getPacketID());
sendPacket(jout);
//triggerSessionEstablished();
}
}
};
JingleTransportListener jingleTransportListener = new JingleTransportListener() {
public void transportEstablished(TransportCandidate local, TransportCandidate remote) {
if (isFullyEstablished()) {
// Indicate that this session is active.
setSessionState(JingleSessionStateActive.getInstance());
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
contentNegotiator.triggerContentEstablished();
}
if (getSessionState().equals(JingleSessionStatePending.getInstance())) {
Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
// Build up a response packet from each media manager.
for (ContentNegotiator contentNegotiator : contentNegotiators) {
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
jout.addContent(contentNegotiator.getJingleContent());
}
// Send the "accept" and wait for the ACK
addExpectedId(jout.getPacketID());
sendPacket(jout);
}
}
}
public void transportClosed(TransportCandidate cand) {
}
public void transportClosedOnError(XMPPException e) {
}
};
addMediaListener(jingleMediaListener);
addTransportListener(jingleTransportListener);
}
// Triggers
/**
* Trigger a session closed event.
*/
protected void triggerSessionClosed(String reason) {
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
//
// contentNegotiator.stopJingleMediaSession();
//
// for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
// candidate.removeCandidateEcho();
// }
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleSessionListener) {
JingleSessionListener sli = (JingleSessionListener) li;
sli.sessionClosed(reason, this);
}
}
close();
}
/**
* Trigger a session closed event due to an error.
*/
protected void triggerSessionClosedOnError(XMPPException exc) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
contentNegotiator.stopJingleMediaSession();
for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
candidate.removeCandidateEcho();
}
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleSessionListener) {
JingleSessionListener sli = (JingleSessionListener) li;
sli.sessionClosedOnError(exc, this);
}
}
close();
}
/**
* Trigger a session established event.
*/
// protected void triggerSessionEstablished() {
// List<JingleListener> listeners = getListenersList();
// for (JingleListener li : listeners) {
// if (li instanceof JingleSessionListener) {
// JingleSessionListener sli = (JingleSessionListener) li;
// sli.sessionEstablished(this);
// }
// }
// }
/**
* Trigger a media received event.
*/
protected void triggerMediaReceived(String participant) {
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleSessionListener) {
JingleSessionListener sli = (JingleSessionListener) li;
sli.sessionMediaReceived(this, participant);
}
}
}
/**
* Trigger a session redirect event.
*/
// protected void triggerSessionRedirect(String arg) {
// List<JingleListener> listeners = getListenersList();
// for (JingleListener li : listeners) {
// if (li instanceof JingleSessionListener) {
// JingleSessionListener sli = (JingleSessionListener) li;
// sli.sessionRedirected(arg, this);
// }
// }
// }
/**
* Trigger a session decline event.
*/
// protected void triggerSessionDeclined(String reason) {
// List<JingleListener> listeners = getListenersList();
// for (JingleListener li : listeners) {
// if (li instanceof JingleSessionListener) {
// JingleSessionListener sli = (JingleSessionListener) li;
// sli.sessionDeclined(reason, this);
// }
// }
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
// for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
// candidate.removeCandidateEcho();
// }
// }
/**
* Terminates the session with default reason.
*
* @throws XMPPException
*/
public void terminate() throws XMPPException {
terminate("Closed Locally");
}
/**
* Terminates the session with a custom reason.
*
* @throws XMPPException
*/
public void terminate(String reason) throws XMPPException {
if (isClosed())
return;
LOGGER.debug("Terminate " + reason);
Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE);
jout.setType(IQ.Type.SET);
sendPacket(jout);
triggerSessionClosed(reason);
}
/**
* Terminate negotiations.
*/
public void close() {
if (isClosed())
return;
// Set the session state to ENDED.
setSessionState(JingleSessionStateEnded.getInstance());
for (ContentNegotiator contentNegotiator : contentNegotiators) {
contentNegotiator.stopJingleMediaSession();
for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
candidate.removeCandidateEcho();
contentNegotiator.close();
}
removePacketListener();
removeConnectionListener();
getConnection().removeConnectionListener(connectionListener);
LOGGER.debug("Negotiation Closed: " + getConnection().getUser() + " " + sid);
super.close();
}
public boolean isClosed() {
return getSessionState().equals(JingleSessionStateEnded.getInstance());
}
// Packet and error creation
/**
* A convience method to create an IQ packet.
*
* @param ID
* The packet ID of the
* @param to
* To whom the packet is addressed.
* @param from
* From whom the packet is sent.
* @param type
* The iq type of the packet.
* @return The created IQ packet.
*/
public static IQ createIQ(String ID, String to, String from, IQ.Type type) {
IQ iqPacket = new IQ() {
public String getChildElementXML() {
return null;
}
};
iqPacket.setPacketID(ID);
iqPacket.setTo(to);
iqPacket.setFrom(from);
iqPacket.setType(type);
return iqPacket;
}
/**
* A convience method to create an error packet.
*
* @param ID
* The packet ID of the
* @param to
* To whom the packet is addressed.
* @param from
* From whom the packet is sent.
* @param errCode
* The error code.
* @param errStr
* The error string.
* @return The created IQ packet.
*/
public static IQ createError(String ID, String to, String from, int errCode, XMPPError error) {
IQ iqError = createIQ(ID, to, from, IQ.Type.ERROR);
iqError.setError(error);
LOGGER.debug("Created Error Packet:" + iqError.toXML());
return iqError;
}
/**
* Complete and send an error. Complete all the null fields in an IQ error
* reponse, using the sesssion information we have or some info from the
* incoming packet.
*
* @param iq
* The Jingle packet we are responing to
* @param error
* the IQ packet we want to complete and send
*/
public IQ createJingleError(IQ iq, JingleError jingleError) {
IQ errorPacket = null;
if (jingleError != null) {
errorPacket = createIQ(getSid(), iq.getFrom(), iq.getTo(), IQ.Type.ERROR);
List<PacketExtension> extList = new ArrayList<PacketExtension>();
extList.add(jingleError);
XMPPError error = new XMPPError(0, XMPPError.Type.CANCEL, jingleError.toString(), "", extList);
// Fill in the fields with the info from the Jingle packet
errorPacket.setPacketID(iq.getPacketID());
errorPacket.setError(error);
// errorPacket.addExtension(jingleError);
// NO! Let the normal state machinery do all of the sending.
// getConnection().sendPacket(perror);
LOGGER.error("Error sent: " + errorPacket.toXML());
}
return errorPacket;
}
/**
* Called when new Media is received.
*/
public void mediaReceived(String participant) {
triggerMediaReceived(participant);
}
/**
* This is the starting point for intitiating a new session.
*
* @throws IllegalStateException
*/
public void startOutgoing() throws IllegalStateException {
updatePacketListener();
setSessionState(JingleSessionStatePending.getInstance());
Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE);
// Create a content negotiator for each media manager on the session.
for (JingleMediaManager mediaManager : getMediaManagers()) {
ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName());
// Create the media negotiator for this content description.
contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg));
JingleTransportManager transportManager = mediaManager.getTransportManager();
TransportResolver resolver = null;
try {
resolver = transportManager.getResolver(this);
} catch (XMPPException e) {
e.printStackTrace();
}
if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg));
}
if (resolver.getType().equals(TransportResolver.Type.ice)) {
contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg));
}
addContentNegotiator(contentNeg);
}
// Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet.
for (ContentNegotiator contentNegotiator : contentNegotiators) {
jingle.addContent(contentNegotiator.getJingleContent());
}
// Save the session-initiate packet ID, so that we can respond to it.
sessionInitPacketID = jingle.getPacketID();
sendPacket(jingle);
// Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
setupListeners();
// Give each of the content negotiators a chance to start
// and return a portion of the structure to make the Jingle packet.
// Don't do this anymore. The problem is that the other side might not be ready.
// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators
// before processing it. (See receivePacketAndRespond() above.
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
// contentNegotiator.start();
// }
}
/**
* This is the starting point for responding to a new session.
*/
public void startIncoming() {
//updatePacketListener();
}
protected void doStart() {
}
/**
* When we initiate a session we need to start a bunch of negotiators right after we receive the result
* packet for our session-initiate. This is where we start them.
*
*/
private void startNegotiators() {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator();
transNeg.start();
}
}
/**
* The jingle session may have one or more media managers that are trying to establish media sessions.
* When the media manager succeeds in creating a media session is registers it with the session by the
* media manager's static name. This routine is where the media manager does the registering.
*/
public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) {
mediaSessionMap.put(mediaManagerName, mediaSession);
}
/**
* The jingle session may have one or more media managers that are trying to establish media sessions.
* When the media manager succeeds in creating a media session is registers it with the session by the
* media manager's static name. This routine is where other objects can access the registered media sessions.
* NB: If the media manager has not succeeded in establishing a media session then this could return null.
*/
public JingleMediaSession getMediaSession(String mediaManagerName) {
return mediaSessionMap.get(mediaManagerName);
}
}