|
|
|
|
@ -7,18 +7,15 @@
|
|
|
|
|
package net.java.sip.communicator.service.protocol.media;
|
|
|
|
|
|
|
|
|
|
import java.beans.*;
|
|
|
|
|
import java.io.*;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
import javax.xml.parsers.*;
|
|
|
|
|
|
|
|
|
|
import net.java.sip.communicator.service.protocol.*;
|
|
|
|
|
import net.java.sip.communicator.service.protocol.event.*;
|
|
|
|
|
import net.java.sip.communicator.util.*;
|
|
|
|
|
|
|
|
|
|
import org.jitsi.service.neomedia.*;
|
|
|
|
|
import org.jitsi.util.xml.*;
|
|
|
|
|
import org.w3c.dom.*;
|
|
|
|
|
import org.xml.sax.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Represents a default implementation of
|
|
|
|
|
@ -33,6 +30,7 @@
|
|
|
|
|
* @param <CalleeAddressT>
|
|
|
|
|
*
|
|
|
|
|
* @author Lyubomir Marinov
|
|
|
|
|
* @author Boris Grozev
|
|
|
|
|
*/
|
|
|
|
|
public abstract class AbstractOperationSetTelephonyConferencing<
|
|
|
|
|
ProtocolProviderServiceT extends ProtocolProviderService,
|
|
|
|
|
@ -748,21 +746,21 @@ else if (RegistrationState.UNREGISTERED.equals(newState))
|
|
|
|
|
/**
|
|
|
|
|
* Updates the conference-related properties of a specific <tt>CallPeer</tt>
|
|
|
|
|
* such as <tt>conferenceFocus</tt> and <tt>conferenceMembers</tt> with
|
|
|
|
|
* information received from it as a conference focus in the form of a
|
|
|
|
|
* conference-info XML document.
|
|
|
|
|
* the information described in <tt>confInfo</tt>.
|
|
|
|
|
* <tt>confInfo</tt> must be a document with "full" state.
|
|
|
|
|
*
|
|
|
|
|
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
|
|
|
|
|
* sent the specified conference-info XML document
|
|
|
|
|
* @param conferenceInfoDocument the conference-info XML document sent by
|
|
|
|
|
* <tt>callPeer</tt> in order to update the conference-related information
|
|
|
|
|
* of the local peer represented by the associated <tt>Call</tt>
|
|
|
|
|
* @param confInfo the conference-info XML document to use to update
|
|
|
|
|
* the conference-related information of the local peer represented
|
|
|
|
|
* by the associated <tt>Call</tt>. It must have a "full" state.
|
|
|
|
|
*/
|
|
|
|
|
private void setConferenceInfoDocument(
|
|
|
|
|
private int setConferenceInfoDocument(
|
|
|
|
|
MediaAwareCallPeerT callPeer,
|
|
|
|
|
Document conferenceInfoDocument)
|
|
|
|
|
ConferenceInfoDocument confInfo)
|
|
|
|
|
{
|
|
|
|
|
NodeList usersList
|
|
|
|
|
= conferenceInfoDocument.getElementsByTagName(ELEMENT_USERS);
|
|
|
|
|
= confInfo.getDocument().getElementsByTagName(ELEMENT_USERS);
|
|
|
|
|
ConferenceMember[] toRemove
|
|
|
|
|
= callPeer.getConferenceMembers().toArray(
|
|
|
|
|
AbstractCallPeer.NO_CONFERENCE_MEMBERS);
|
|
|
|
|
@ -888,6 +886,9 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
|
|
|
|
|
|
|
|
|
|
if (changed)
|
|
|
|
|
notifyAll(callPeer.getCall());
|
|
|
|
|
|
|
|
|
|
callPeer.setLastConferenceInfoReceived(confInfo);
|
|
|
|
|
return confInfo.getVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -898,9 +899,6 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
|
|
|
|
|
*
|
|
|
|
|
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
|
|
|
|
|
* sent the specified conference-info XML document
|
|
|
|
|
* @param version the value of the <tt>version</tt> attribute of the
|
|
|
|
|
* <tt>conference-info</tt> XML element currently represented in the
|
|
|
|
|
* specified <tt>callPeer</tt>
|
|
|
|
|
* @param conferenceInfoXML the conference-info XML document sent by
|
|
|
|
|
* <tt>callPeer</tt> in order to update the conference-related information
|
|
|
|
|
* of the local peer represented by the associated <tt>Call</tt>
|
|
|
|
|
@ -908,89 +906,566 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
|
|
|
|
|
* <tt>conference-info</tt> XML element of the specified
|
|
|
|
|
* <tt>conferenceInfoXML</tt> if it was successfully parsed and represented
|
|
|
|
|
* in the specified <tt>callPeer</tt>
|
|
|
|
|
*
|
|
|
|
|
* @throws XMLException If <tt>conferenceInfoXML</tt> couldn't be parsed as
|
|
|
|
|
* a <tt>ConferenceInfoDocument</tt>
|
|
|
|
|
*/
|
|
|
|
|
protected int setConferenceInfoXML(
|
|
|
|
|
MediaAwareCallPeerT callPeer,
|
|
|
|
|
int version,
|
|
|
|
|
String conferenceInfoXML)
|
|
|
|
|
throws XMLException
|
|
|
|
|
{
|
|
|
|
|
byte[] bytes;
|
|
|
|
|
ConferenceInfoDocument confInfo
|
|
|
|
|
= new ConferenceInfoDocument(conferenceInfoXML);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
/*
|
|
|
|
|
* The CallPeer sent conference-info XML so we're sure it's a
|
|
|
|
|
* conference focus.
|
|
|
|
|
*/
|
|
|
|
|
callPeer.setConferenceFocus(true);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The following implements the procedure outlined in section 4.6 of
|
|
|
|
|
* RFC4575 - Constructing Coherent State
|
|
|
|
|
*/
|
|
|
|
|
int documentVersion = confInfo.getVersion();
|
|
|
|
|
int ourVersion = callPeer.getLastConferenceInfoReceivedVersion();
|
|
|
|
|
ConferenceInfoDocument.State documentState = confInfo.getState();
|
|
|
|
|
|
|
|
|
|
if (ourVersion == -1)
|
|
|
|
|
{
|
|
|
|
|
bytes = conferenceInfoXML.getBytes("UTF-8");
|
|
|
|
|
if (documentState == ConferenceInfoDocument.State.FULL)
|
|
|
|
|
{
|
|
|
|
|
return setConferenceInfoDocument(callPeer, confInfo);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
logger.warn("Received a conference-info document with state '"
|
|
|
|
|
+ documentState + "'. Cannot apply it, because we haven't"
|
|
|
|
|
+ "initialized a local document yet. Sending peer: "
|
|
|
|
|
+ callPeer);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (UnsupportedEncodingException uee)
|
|
|
|
|
else if (documentVersion <= ourVersion)
|
|
|
|
|
{
|
|
|
|
|
logger
|
|
|
|
|
.warn(
|
|
|
|
|
"Failed to gets bytes from String for the UTF-8 charset",
|
|
|
|
|
uee);
|
|
|
|
|
bytes = conferenceInfoXML.getBytes();
|
|
|
|
|
if (logger.isInfoEnabled())
|
|
|
|
|
{
|
|
|
|
|
logger.info("Received a stale conference-info document. Local "
|
|
|
|
|
+ "version " + ourVersion + ", document version "
|
|
|
|
|
+ documentVersion + ". Sending peer: " + callPeer);
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else //ourVersion != -1 && ourVersion < documentVersion
|
|
|
|
|
{
|
|
|
|
|
if (documentState == ConferenceInfoDocument.State.FULL)
|
|
|
|
|
return setConferenceInfoDocument(callPeer, confInfo);
|
|
|
|
|
else if (documentState == ConferenceInfoDocument.State.DELETED)
|
|
|
|
|
{
|
|
|
|
|
logger.warn("Received a conference-info document with state" +
|
|
|
|
|
"'deleted', can't handle. Sending peer: " + callPeer);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else if (documentState == ConferenceInfoDocument.State.PARTIAL)
|
|
|
|
|
{
|
|
|
|
|
if (documentVersion == ourVersion+1)
|
|
|
|
|
return updateConferenceInfoDocument(callPeer, confInfo);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* According to RFC4575 we "MUST generate a subscription
|
|
|
|
|
* refresh request to trigger a full state notification".
|
|
|
|
|
*/
|
|
|
|
|
logger.warn("Received a Conference Information document "
|
|
|
|
|
+ "with state '" + documentState + "' and version "
|
|
|
|
|
+ documentVersion + ". Cannon apply it, because local "
|
|
|
|
|
+ "version is " + ourVersion + ". Sending peer: "
|
|
|
|
|
+ callPeer);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return -1; //unreachable
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes the parameters (specified after a semicolon) from a specific
|
|
|
|
|
* address <tt>String</tt> if any are present in it.
|
|
|
|
|
*
|
|
|
|
|
* @param address the <tt>String</tt> value representing an address from
|
|
|
|
|
* which any parameters are to be removed
|
|
|
|
|
* @return a <tt>String</tt> representing the specified <tt>address</tt>
|
|
|
|
|
* without any parameters
|
|
|
|
|
*/
|
|
|
|
|
public static String stripParametersFromAddress(String address)
|
|
|
|
|
{
|
|
|
|
|
if (address != null)
|
|
|
|
|
{
|
|
|
|
|
int parametersBeginIndex = address.indexOf(';');
|
|
|
|
|
|
|
|
|
|
Document doc = null;
|
|
|
|
|
Throwable exception = null;
|
|
|
|
|
if (parametersBeginIndex > -1)
|
|
|
|
|
address = address.substring(0, parametersBeginIndex);
|
|
|
|
|
}
|
|
|
|
|
return address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a <tt>ConferenceInfoDocument</tt> which describes the current
|
|
|
|
|
* state of the conference in which <tt>callPeer</tt> participates. The
|
|
|
|
|
* created document contains a "full" description (as opposed to a partial
|
|
|
|
|
* description, see RFC4575).
|
|
|
|
|
*
|
|
|
|
|
* @return a <tt>ConferenceInfoDocument</tt> which describes the current
|
|
|
|
|
* state of the conference in which this <tt>CallPeer</tt> participates.
|
|
|
|
|
*/
|
|
|
|
|
protected ConferenceInfoDocument getCurrentConferenceInfo(
|
|
|
|
|
MediaAwareCallPeer<?,?,?> callPeer)
|
|
|
|
|
{
|
|
|
|
|
ConferenceInfoDocument confInfo;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
doc
|
|
|
|
|
= DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
|
|
|
|
.parse(new ByteArrayInputStream(bytes));
|
|
|
|
|
confInfo = new ConferenceInfoDocument();
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ioe)
|
|
|
|
|
catch (XMLException e)
|
|
|
|
|
{
|
|
|
|
|
exception = ioe;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
catch (ParserConfigurationException pce)
|
|
|
|
|
confInfo.setState(ConferenceInfoDocument.State.FULL);
|
|
|
|
|
confInfo.setEntity(getLocalEntity(callPeer));
|
|
|
|
|
|
|
|
|
|
Call call = callPeer.getCall();
|
|
|
|
|
List<CallPeer> conferenceCallPeers = CallConference.getCallPeers(call);
|
|
|
|
|
confInfo.setUserCount(
|
|
|
|
|
1 /* the local peer/user */ + conferenceCallPeers.size());
|
|
|
|
|
|
|
|
|
|
/* The local user */
|
|
|
|
|
addPeerToConferenceInfo(confInfo, callPeer, false);
|
|
|
|
|
|
|
|
|
|
/* Remote users */
|
|
|
|
|
for (CallPeer conferenceCallPeer : conferenceCallPeers)
|
|
|
|
|
{
|
|
|
|
|
exception = pce;
|
|
|
|
|
if (conferenceCallPeer instanceof MediaAwareCallPeer<?,?,?>)
|
|
|
|
|
addPeerToConferenceInfo(
|
|
|
|
|
confInfo,
|
|
|
|
|
(MediaAwareCallPeer<?,?,?>)conferenceCallPeer,
|
|
|
|
|
true);
|
|
|
|
|
}
|
|
|
|
|
catch (SAXException saxe)
|
|
|
|
|
|
|
|
|
|
return confInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds a <tt>user</tt> element to <tt>confInfo</tt> which describes
|
|
|
|
|
* <tt>callPeer</tt>, or the local peer if <tt>remote</tt> is <tt>false</tt>.
|
|
|
|
|
*
|
|
|
|
|
* @param confInfo the <tt>ConferenceInformationDocument</tt> to which to
|
|
|
|
|
* add a <tt>user</tt> element
|
|
|
|
|
* @param callPeer the <tt>CallPeer</tt> which should be described
|
|
|
|
|
* @param remote <tt>true</tt> to describe <tt>callPeer</tt>, or
|
|
|
|
|
* <tt>false</tt> to describe the local peer.
|
|
|
|
|
*/
|
|
|
|
|
private void addPeerToConferenceInfo(
|
|
|
|
|
ConferenceInfoDocument confInfo,
|
|
|
|
|
MediaAwareCallPeer<?,?,?> callPeer,
|
|
|
|
|
boolean remote)
|
|
|
|
|
{
|
|
|
|
|
String entity
|
|
|
|
|
= remote
|
|
|
|
|
? callPeer.getEntity()
|
|
|
|
|
: getLocalEntity(callPeer);
|
|
|
|
|
ConferenceInfoDocument.User user = confInfo.addNewUser(entity);
|
|
|
|
|
|
|
|
|
|
String displayName
|
|
|
|
|
= remote
|
|
|
|
|
? callPeer.getDisplayName()
|
|
|
|
|
: getLocalDisplayName();
|
|
|
|
|
user.setDisplayText(displayName);
|
|
|
|
|
|
|
|
|
|
ConferenceInfoDocument.Endpoint endpoint
|
|
|
|
|
= user.addNewEndpoint(entity);
|
|
|
|
|
|
|
|
|
|
endpoint.setStatus(
|
|
|
|
|
remote
|
|
|
|
|
? getEndpointStatus(callPeer)
|
|
|
|
|
: ConferenceInfoDocument.EndpointStatusType.connected);
|
|
|
|
|
|
|
|
|
|
CallPeerMediaHandler<?> mediaHandler
|
|
|
|
|
= callPeer.getMediaHandler();
|
|
|
|
|
|
|
|
|
|
for (MediaType mediaType : MediaType.values())
|
|
|
|
|
{
|
|
|
|
|
exception = saxe;
|
|
|
|
|
MediaStream stream = mediaHandler.getStream(mediaType);
|
|
|
|
|
if (stream != null)
|
|
|
|
|
{
|
|
|
|
|
ConferenceInfoDocument.Media media
|
|
|
|
|
= endpoint.addNewMedia(mediaType.toString());
|
|
|
|
|
long srcId
|
|
|
|
|
= remote
|
|
|
|
|
? getRemoteSourceID(callPeer, mediaType)
|
|
|
|
|
: stream.getLocalSourceID();
|
|
|
|
|
|
|
|
|
|
if (srcId != -1)
|
|
|
|
|
media.setSrcId(Long.toString(srcId));
|
|
|
|
|
|
|
|
|
|
media.setType(mediaType.toString());
|
|
|
|
|
|
|
|
|
|
MediaDirection direction
|
|
|
|
|
= remote
|
|
|
|
|
? getRemoteDirection(callPeer, mediaType)
|
|
|
|
|
: stream.getDirection();
|
|
|
|
|
|
|
|
|
|
if (direction == null)
|
|
|
|
|
direction = MediaDirection.INACTIVE;
|
|
|
|
|
|
|
|
|
|
media.setStatus(direction.toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (exception != null)
|
|
|
|
|
logger.error("Failed to parse conference-info XML", exception);
|
|
|
|
|
else
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a string to be used for the <tt>entity</tt> attribute of the
|
|
|
|
|
* <tt>user</tt> element for the local peer, in a Conference Information
|
|
|
|
|
* document to be sent to <tt>callPeer</tt>
|
|
|
|
|
*
|
|
|
|
|
* @param callPeer The <tt>CallPeer</tt> for which we are creating a
|
|
|
|
|
* Conference Information document.
|
|
|
|
|
* @return a string to be used for the <tt>entity</tt> attribute of the
|
|
|
|
|
* <tt>user</tt> element for the local peer, in a Conference Information
|
|
|
|
|
* document to be sent to <tt>callPeer</tt>
|
|
|
|
|
*/
|
|
|
|
|
protected abstract String getLocalEntity(CallPeer callPeer);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the display name for the local peer, which is to be used when
|
|
|
|
|
* we send Conference Information.
|
|
|
|
|
* @return the display name for the local peer, which is to be used when
|
|
|
|
|
* we send Conference Information.
|
|
|
|
|
*/
|
|
|
|
|
protected abstract String getLocalDisplayName();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the <tt>EndpointStatusType</tt> to use when describing
|
|
|
|
|
* <tt>callPeer</tt> in a Conference Information document.
|
|
|
|
|
*
|
|
|
|
|
* @param callPeer the <tt>CallPeer</tt> which is to get its state described
|
|
|
|
|
* in a <tt>status</tt> XML element of an <tt>endpoint</tt> XML element
|
|
|
|
|
* @return the <tt>EndpointStatusType</tt> to use when describing
|
|
|
|
|
* <tt>callPeer</tt> in a Conference Information document.
|
|
|
|
|
*/
|
|
|
|
|
private ConferenceInfoDocument.EndpointStatusType getEndpointStatus(
|
|
|
|
|
CallPeer callPeer)
|
|
|
|
|
{
|
|
|
|
|
CallPeerState callPeerState = callPeer.getState();
|
|
|
|
|
|
|
|
|
|
if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.alerting;
|
|
|
|
|
if (CallPeerState.CONNECTING.equals(callPeerState)
|
|
|
|
|
|| CallPeerState
|
|
|
|
|
.CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.pending;
|
|
|
|
|
if (CallPeerState.DISCONNECTED.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.disconnected;
|
|
|
|
|
if (CallPeerState.INCOMING_CALL.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.dialing_in;
|
|
|
|
|
if (CallPeerState.INITIATING_CALL.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.dialing_out;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* he/she is neither "hearing" the conference mix nor is his/her
|
|
|
|
|
* media being mixed in the conference
|
|
|
|
|
*/
|
|
|
|
|
if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState)
|
|
|
|
|
|| CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.on_hold;
|
|
|
|
|
if (CallPeerState.CONNECTED.equals(callPeerState))
|
|
|
|
|
return ConferenceInfoDocument.EndpointStatusType.connected;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param from A document with state <tt>full</tt> from which to generate a
|
|
|
|
|
* "diff".
|
|
|
|
|
* @param to A document with state <tt>full</tt> to which to generate a
|
|
|
|
|
* "diff"
|
|
|
|
|
* @return a <tt>ConferenceInfoDocument</tt>, such that when it is applied
|
|
|
|
|
* to <tt>from</tt> using the procedure defined in section 4.6 of RFC4575,
|
|
|
|
|
* the result is <tt>to</tt>. May return <tt>null</tt> if <tt>from</tt> and
|
|
|
|
|
* <tt>to</tt> are not found to be different (that is, in case no document
|
|
|
|
|
* needs to be sent)
|
|
|
|
|
*/
|
|
|
|
|
protected ConferenceInfoDocument getConferenceInfoDiff(
|
|
|
|
|
ConferenceInfoDocument from,
|
|
|
|
|
ConferenceInfoDocument to)
|
|
|
|
|
throws IllegalArgumentException
|
|
|
|
|
{
|
|
|
|
|
if (from.getState() != ConferenceInfoDocument.State.FULL)
|
|
|
|
|
throw new IllegalArgumentException("The 'from' document needs to "
|
|
|
|
|
+ "have state=full");
|
|
|
|
|
if (to.getState() != ConferenceInfoDocument.State.FULL)
|
|
|
|
|
throw new IllegalArgumentException("The 'to' document needs to "
|
|
|
|
|
+ "have state=full");
|
|
|
|
|
|
|
|
|
|
if (conferenceInfoDocumentsMatch(from, to))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return to;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the conference-related properties of a specific <tt>CallPeer</tt>
|
|
|
|
|
* such as <tt>conferenceFocus</tt> and <tt>conferenceMembers</tt> with
|
|
|
|
|
* information received from it as a conference focus in the form of a
|
|
|
|
|
* partial conference-info XML document.
|
|
|
|
|
*
|
|
|
|
|
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
|
|
|
|
|
* sent the specified partial conference-info XML document
|
|
|
|
|
* @param diff the partial conference-info XML document sent by
|
|
|
|
|
* <tt>callPeer</tt> in order to update the conference-related information
|
|
|
|
|
* of the local peer represented by the associated <tt>Call</tt>
|
|
|
|
|
* @return the value of the <tt>version</tt> attribute of the
|
|
|
|
|
* <tt>conference-info</tt> XML element of the specified
|
|
|
|
|
* <tt>conferenceInfoXML</tt> if it was successfully parsed and represented
|
|
|
|
|
* in the specified <tt>callPeer</tt>
|
|
|
|
|
*/
|
|
|
|
|
private int updateConferenceInfoDocument(
|
|
|
|
|
MediaAwareCallPeerT callPeer,
|
|
|
|
|
ConferenceInfoDocument diff)
|
|
|
|
|
{
|
|
|
|
|
logger.warn("Received a conference-info partial notification, which we"
|
|
|
|
|
+ " can't handle. Sending peer: " + callPeer);
|
|
|
|
|
if (true)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
ConferenceInfoDocument ourDocument
|
|
|
|
|
= callPeer.getLastConferenceInfoReceived();
|
|
|
|
|
ConferenceInfoDocument newDocument;
|
|
|
|
|
|
|
|
|
|
ConferenceInfoDocument.State usersState = diff.getUsersState();
|
|
|
|
|
if (usersState == ConferenceInfoDocument.State.FULL)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* The CallPeer sent conference-info XML so we're sure it's a
|
|
|
|
|
* conference focus.
|
|
|
|
|
*/
|
|
|
|
|
callPeer.setConferenceFocus(true);
|
|
|
|
|
//if users is 'full', all its children must be full
|
|
|
|
|
newDocument = diff;
|
|
|
|
|
newDocument.setState(ConferenceInfoDocument.State.FULL);
|
|
|
|
|
}
|
|
|
|
|
else if (usersState == ConferenceInfoDocument.State.DELETED)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
newDocument = new ConferenceInfoDocument();
|
|
|
|
|
}
|
|
|
|
|
catch (XMLException e)
|
|
|
|
|
{
|
|
|
|
|
logger.warn("Could not create a new ConferenceInfoDocument", e);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newDocument.setVersion(diff.getVersion());
|
|
|
|
|
newDocument.setEntity(diff.getEntity());
|
|
|
|
|
newDocument.setUserCount(diff.getUserCount());
|
|
|
|
|
}
|
|
|
|
|
else //'partial'
|
|
|
|
|
{
|
|
|
|
|
newDocument = ourDocument;
|
|
|
|
|
|
|
|
|
|
int documentVersion
|
|
|
|
|
= Integer.parseInt(
|
|
|
|
|
doc.getDocumentElement().getAttribute("version"));
|
|
|
|
|
newDocument.setVersion(diff.getVersion());
|
|
|
|
|
newDocument.setEntity(diff.getEntity());
|
|
|
|
|
newDocument.setUserCount(diff.getUserCount());
|
|
|
|
|
|
|
|
|
|
if ((version == -1) || (documentVersion >= version))
|
|
|
|
|
for (ConferenceInfoDocument.User user : diff.getUsers())
|
|
|
|
|
{
|
|
|
|
|
setConferenceInfoDocument(callPeer, doc);
|
|
|
|
|
return documentVersion;
|
|
|
|
|
ConferenceInfoDocument.State userState = user.getState();
|
|
|
|
|
if (userState == ConferenceInfoDocument.State.FULL)
|
|
|
|
|
{
|
|
|
|
|
//copy the whole thing from diff to newDocument
|
|
|
|
|
}
|
|
|
|
|
else if (userState == ConferenceInfoDocument.State.DELETED)
|
|
|
|
|
{
|
|
|
|
|
newDocument.removeUser(user.getEntity());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ConferenceInfoDocument.User ourUser
|
|
|
|
|
= newDocument.getUser(user.getEntity());
|
|
|
|
|
for (ConferenceInfoDocument.Endpoint endpoint
|
|
|
|
|
: user.getEndpoints())
|
|
|
|
|
{
|
|
|
|
|
ConferenceInfoDocument.State endpointState
|
|
|
|
|
= endpoint.getState();
|
|
|
|
|
if (endpointState == ConferenceInfoDocument.State.FULL)
|
|
|
|
|
{
|
|
|
|
|
//update the whole thing
|
|
|
|
|
}
|
|
|
|
|
else if (endpointState
|
|
|
|
|
== ConferenceInfoDocument.State.DELETED)
|
|
|
|
|
{
|
|
|
|
|
ourUser.removeEndpoint(endpoint.getEntity());
|
|
|
|
|
}
|
|
|
|
|
else //'partial'
|
|
|
|
|
{
|
|
|
|
|
for (ConferenceInfoDocument.Media media
|
|
|
|
|
: endpoint.getMedias())
|
|
|
|
|
{
|
|
|
|
|
//copy media with id media.getId()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes the parameters (specified after a semicolon) from a specific
|
|
|
|
|
* address <tt>String</tt> if any are present in it.
|
|
|
|
|
* @param a A document with state <tt>full</tt> which to compare to
|
|
|
|
|
* <tt>b</tt>
|
|
|
|
|
* @param b A document with state <tt>full</tt> which to compare to
|
|
|
|
|
* <tt>a</tt>
|
|
|
|
|
* @return <tt>false</tt> if the two documents are found to be different,
|
|
|
|
|
* <tt>true</tt> otherwise (that is, it can return true for non-identical
|
|
|
|
|
* documents).
|
|
|
|
|
*/
|
|
|
|
|
private boolean conferenceInfoDocumentsMatch(
|
|
|
|
|
ConferenceInfoDocument a,
|
|
|
|
|
ConferenceInfoDocument b)
|
|
|
|
|
{
|
|
|
|
|
if (a.getState() != ConferenceInfoDocument.State.FULL)
|
|
|
|
|
throw new IllegalArgumentException("The 'a' document needs to"
|
|
|
|
|
+ "have state=full");
|
|
|
|
|
if (b.getState() != ConferenceInfoDocument.State.FULL)
|
|
|
|
|
throw new IllegalArgumentException("The 'b' document needs to"
|
|
|
|
|
+ "have state=full");
|
|
|
|
|
|
|
|
|
|
if (!stringsMatch(a.getEntity(), b.getEntity()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (a.getUserCount() != b.getUserCount())
|
|
|
|
|
return false;
|
|
|
|
|
else if (a.getUsers().size() != b.getUsers().size())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for(ConferenceInfoDocument.User aUser : a.getUsers())
|
|
|
|
|
{
|
|
|
|
|
if (!usersMatch(aUser, b.getUser(aUser.getEntity())))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks whether two <tt>ConferenceInfoDocument.User</tt> instances
|
|
|
|
|
* match according to the needs of our implementation. Can return
|
|
|
|
|
* <tt>true</tt> for users which are not identical.
|
|
|
|
|
*
|
|
|
|
|
* @param address the <tt>String</tt> value representing an address from
|
|
|
|
|
* which any parameters are to be removed
|
|
|
|
|
* @return a <tt>String</tt> representing the specified <tt>address</tt>
|
|
|
|
|
* without any parameters
|
|
|
|
|
* @param a A <tt>ConferenceInfoDocument.User</tt> to compare
|
|
|
|
|
* @param b A <tt>ConferenceInfoDocument.User</tt> to compare
|
|
|
|
|
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
|
|
|
|
|
* different in a way that is significant for our needs, <tt>true</tt>
|
|
|
|
|
* otherwise.
|
|
|
|
|
*/
|
|
|
|
|
protected static String stripParametersFromAddress(String address)
|
|
|
|
|
private boolean usersMatch(
|
|
|
|
|
ConferenceInfoDocument.User a,
|
|
|
|
|
ConferenceInfoDocument.User b)
|
|
|
|
|
{
|
|
|
|
|
if (address != null)
|
|
|
|
|
if (a == null && b == null)
|
|
|
|
|
return true;
|
|
|
|
|
else if (a == null || b == null)
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getEntity(), b.getEntity()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getDisplayText(), b.getDisplayText()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (a.getEndpoints().size() != b.getEndpoints().size())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (ConferenceInfoDocument.Endpoint aEndpoint : a.getEndpoints())
|
|
|
|
|
{
|
|
|
|
|
int parametersBeginIndex = address.indexOf(';');
|
|
|
|
|
if (!endpointsMatch(aEndpoint, b.getEndpoint(aEndpoint.getEntity())))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parametersBeginIndex > -1)
|
|
|
|
|
address = address.substring(0, parametersBeginIndex);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks whether two <tt>ConferenceInfoDocument.Endpoint</tt> instances
|
|
|
|
|
* match according to the needs of our implementation. Can return
|
|
|
|
|
* <tt>true</tt> for endpoints which are not identical.
|
|
|
|
|
*
|
|
|
|
|
* @param a A <tt>ConferenceInfoDocument.Endpoint</tt> to compare
|
|
|
|
|
* @param b A <tt>ConferenceInfoDocument.Endpoint</tt> to compare
|
|
|
|
|
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
|
|
|
|
|
* different in a way that is significant for our needs, <tt>true</tt>
|
|
|
|
|
* otherwise.
|
|
|
|
|
*/
|
|
|
|
|
private boolean endpointsMatch(
|
|
|
|
|
ConferenceInfoDocument.Endpoint a,
|
|
|
|
|
ConferenceInfoDocument.Endpoint b)
|
|
|
|
|
{
|
|
|
|
|
if (a == null && b == null)
|
|
|
|
|
return true;
|
|
|
|
|
else if (a == null || b == null)
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getEntity(), b.getEntity()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (a.getStatus() != b.getStatus())
|
|
|
|
|
return false;
|
|
|
|
|
else if (a.getMedias().size() != b.getMedias().size())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (ConferenceInfoDocument.Media aMedia : a.getMedias())
|
|
|
|
|
{
|
|
|
|
|
if (!mediasMatch(aMedia, b.getMedia(aMedia.getId())))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return address;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks whether two <tt>ConferenceInfoDocument.Media</tt> instances
|
|
|
|
|
* match according to the needs of our implementation. Can return
|
|
|
|
|
* <tt>true</tt> for endpoints which are not identical.
|
|
|
|
|
*
|
|
|
|
|
* @param a A <tt>ConferenceInfoDocument.Media</tt> to compare
|
|
|
|
|
* @param b A <tt>ConferenceInfoDocument.Media</tt> to compare
|
|
|
|
|
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
|
|
|
|
|
* different in a way that is significant for our needs, <tt>true</tt>
|
|
|
|
|
* otherwise.
|
|
|
|
|
*/
|
|
|
|
|
private boolean mediasMatch(
|
|
|
|
|
ConferenceInfoDocument.Media a,
|
|
|
|
|
ConferenceInfoDocument.Media b)
|
|
|
|
|
{
|
|
|
|
|
if (a == null && b == null)
|
|
|
|
|
return true;
|
|
|
|
|
else if (a == null || b == null)
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getId(), b.getId()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getSrcId(), b.getSrcId()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getType(), b.getType()))
|
|
|
|
|
return false;
|
|
|
|
|
else if (!stringsMatch(a.getStatus(), b.getStatus()))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param a A <tt>String</tt> to compare to <tt>b</tt>
|
|
|
|
|
* @param b A <tt>String</tt> to compare to <tt>a</tt>
|
|
|
|
|
* @return <tt>true</tt> if and only if <tt>a</tt> and <tt>b</tt> are both
|
|
|
|
|
* <tt>null</tt>, or they are equal as <tt>String</tt>s
|
|
|
|
|
*/
|
|
|
|
|
private boolean stringsMatch(String a, String b)
|
|
|
|
|
{
|
|
|
|
|
if (a == null && b == null)
|
|
|
|
|
return true;
|
|
|
|
|
else if (a == null || b == null)
|
|
|
|
|
return false;
|
|
|
|
|
return a.equals(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|