/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.protocol.jabber;
import java.util.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.caps.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.util.*;
/**
* Represents an <tt>OperationSet</tt> to query the <tt>OperationSet</tt>s
* supported for a specific Jabber <tt>Contact</tt>. The <tt>OperationSet</tt>s
* reported as supported for a specific Jabber <tt>Contact</tt> are considered
* by the associated protocol provider to be capabilities possessed by the
* Jabber <tt>Contact</tt> in question.
*
* @author Lyubomir Marinov
* @author Yana Stamcheva
*/
public class OperationSetContactCapabilitiesJabberImpl
extends AbstractOperationSetContactCapabilities<ProtocolProviderServiceJabberImpl>
implements UserCapsNodeListener,
ContactPresenceStatusListener
{
/**
* The <tt>Logger</tt> used by the
* <tt>OperationSetContactCapabilitiesJabberImpl</tt> class and its
* instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(OperationSetContactCapabilitiesJabberImpl.class);
/**
* The name of the property used to control whether to use
* all resources to show capabilities
*/
public static final String PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES =
"net.java.sip.communicator.XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES";
/**
* The default value for the capabilities setting
*/
public static final boolean USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT =
true;
/**
* The list of <tt>OperationSet</tt> capabilities presumed to be supported
* by a <tt>Contact</tt> when it is offline.
*/
private static final Set<Class<? extends OperationSet>>
OFFLINE_OPERATION_SETS
= new HashSet<Class<? extends OperationSet>>();
/**
* The <tt>Map</tt> which associates specific <tt>OperationSet</tt> classes
* with the features to be supported by a <tt>Contact</tt> in order to
* consider the <tt>Contact</tt> to possess the respective
* <tt>OperationSet</tt> capability.
*/
private static final Map<Class<? extends OperationSet>, String[]>
OPERATION_SETS_TO_FEATURES
= new HashMap<Class<? extends OperationSet>, String[]>();
static
{
OFFLINE_OPERATION_SETS.add(OperationSetBasicInstantMessaging.class);
OFFLINE_OPERATION_SETS.add(OperationSetMessageCorrection.class);
OFFLINE_OPERATION_SETS.add(OperationSetServerStoredContactInfo.class);
OPERATION_SETS_TO_FEATURES.put(
OperationSetBasicTelephony.class,
new String[]
{
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP_AUDIO
});
OPERATION_SETS_TO_FEATURES.put(
OperationSetVideoTelephony.class,
new String[]
{
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP_VIDEO
});
OPERATION_SETS_TO_FEATURES.put(
OperationSetDesktopSharingServer.class,
new String[]
{
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP,
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP_VIDEO
});
OPERATION_SETS_TO_FEATURES.put(
OperationSetMessageCorrection.class,
new String[]
{
MessageCorrectionExtension.NAMESPACE
});
}
/**
* The <tt>EntityCapsManager</tt> associated with the
* <tt>discoveryManager</tt> of {@link #parentProvider}.
*/
private EntityCapsManager capsManager;
/**
* Initializes a new <tt>OperationSetContactCapabilitiesJabberImpl</tt>
* instance which is to be provided by a specific
* <tt>ProtocolProviderServiceJabberImpl</tt>.
*
* @param parentProvider the <tt>ProtocolProviderServiceJabberImpl</tt>
* which will provide the new instance
*/
public OperationSetContactCapabilitiesJabberImpl(
ProtocolProviderServiceJabberImpl parentProvider)
{
super(parentProvider);
OperationSetPresence presenceOpSet
= parentProvider.getOperationSet(OperationSetPresence.class);
if (presenceOpSet != null)
presenceOpSet.addContactPresenceStatusListener(this);
}
/**
* Gets the <tt>OperationSet</tt> corresponding to the specified
* <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the
* returned value is non-<tt>null</tt>, it indicates that the
* <tt>Contact</tt> is considered by the associated protocol provider to
* possess the <tt>opsetClass</tt> capability. Otherwise, the associated
* protocol provider considers <tt>contact</tt> to not have the
* <tt>opsetClass</tt> capability.
*
* @param <U> the type extending <tt>OperationSet</tt> for which the
* specified <tt>contact</tt> is to be checked whether it possesses it as a
* capability
* @param contact the <tt>Contact</tt> for which the <tt>opsetClass</tt>
* capability is to be queried
* @param opsetClass the <tt>OperationSet</tt> <tt>Class</tt> for which the
* specified <tt>contact</tt> is to be checked whether it possesses it as a
* capability
* @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise,
* <tt>false</tt>
* @return the <tt>OperationSet</tt> corresponding to the specified
* <tt>opsetClass</tt> which is considered by the associated protocol
* provider to be possessed as a capability by the specified
* <tt>contact</tt>; otherwise, <tt>null</tt>
* @see AbstractOperationSetContactCapabilities#getOperationSet(Contact,
* Class)
*/
@Override
protected <U extends OperationSet> U getOperationSet(
Contact contact,
Class<U> opsetClass,
boolean online)
{
String jid = parentProvider.getFullJid(contact);
if (jid == null)
jid = contact.getAddress();
return getOperationSet(jid, opsetClass, online);
}
/**
* Gets the <tt>OperationSet</tt>s supported by a specific <tt>Contact</tt>.
* The returned <tt>OperationSet</tt>s are considered by the associated
* protocol provider to capabilities possessed by the specified
* <tt>contact</tt>.
*
* @param contact the <tt>Contact</tt> for which the supported
* <tt>OperationSet</tt> capabilities are to be retrieved
* @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise,
* <tt>false</tt>
* @return a <tt>Map</tt> listing the <tt>OperationSet</tt>s considered by
* the associated protocol provider to be supported by the specified
* <tt>contact</tt> (i.e. to be possessed as capabilities). Each supported
* <tt>OperationSet</tt> capability is represented by a <tt>Map.Entry</tt>
* with key equal to the <tt>OperationSet</tt> class name and value equal to
* the respective <tt>OperationSet</tt> instance
* @see AbstractOperationSetContactCapabilities#getSupportedOperationSets(
* Contact)
*/
@Override
protected Map<String, OperationSet> getSupportedOperationSets(
Contact contact,
boolean online)
{
String jid = parentProvider.getFullJid(contact);
if (jid == null)
jid = contact.getAddress();
return getSupportedOperationSets(jid, online);
}
/**
* Gets the <tt>OperationSet</tt>s supported by a specific <tt>Contact</tt>.
* The returned <tt>OperationSet</tt>s are considered by the associated
* protocol provider to capabilities possessed by the specified
* <tt>contact</tt>.
*
* @param jid the <tt>Contact</tt> for which the supported
* <tt>OperationSet</tt> capabilities are to be retrieved
* @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise,
* <tt>false</tt>
* @return a <tt>Map</tt> listing the <tt>OperationSet</tt>s considered by
* the associated protocol provider to be supported by the specified
* <tt>contact</tt> (i.e. to be possessed as capabilities). Each supported
* <tt>OperationSet</tt> capability is represented by a <tt>Map.Entry</tt>
* with key equal to the <tt>OperationSet</tt> class name and value equal to
* the respective <tt>OperationSet</tt> instance
* @see AbstractOperationSetContactCapabilities#getSupportedOperationSets(
* Contact)
*/
@SuppressWarnings("unchecked")
private Map<String, OperationSet> getSupportedOperationSets(String jid,
boolean online)
{
Map<String, OperationSet> supportedOperationSets
= parentProvider.getSupportedOperationSets();
int supportedOperationSetCount = supportedOperationSets.size();
Map<String, OperationSet> contactSupportedOperationSets
= new HashMap<String, OperationSet>(supportedOperationSetCount);
if (supportedOperationSetCount != 0)
{
for (Map.Entry<String, OperationSet> supportedOperationSetEntry
: supportedOperationSets.entrySet())
{
String opsetClassName = supportedOperationSetEntry.getKey();
Class<? extends OperationSet> opsetClass;
try
{
opsetClass
= (Class<? extends OperationSet>)
Class.forName(opsetClassName);
}
catch (ClassNotFoundException cnfex)
{
opsetClass = null;
logger.error(
"Failed to get OperationSet class for name: "
+ opsetClassName,
cnfex);
}
if (opsetClass != null)
{
OperationSet opset
= getOperationSet(jid, opsetClass, online);
if (opset != null)
{
contactSupportedOperationSets.put(
opsetClassName,
opset);
}
}
}
}
return contactSupportedOperationSets;
}
/**
* Gets the largest set of <tt>OperationSet</tt>s supported from a
* list of full JIDs. The returned <tt>OperationSet</tt>s are considered
* by the associated protocol provider to capabilities possessed by the
* specified <tt>contact</tt>.
*
* @param fullJids a list of full JIDs in which to find the resource with
* the most capabilities.
* @return the <tt>Map</tt> listing the most <tt>OperationSet</tt>s
* considered by the associated protocol provider to be supported by the
* specified <tt>contact</tt> (i.e. to be possessed as capabilities).
* Each supported <tt>OperationSet</tt> capability is represented by a
* <tt>Map.Entry</tt> with key equal to the <tt>OperationSet</tt> class
* name and value equal to the respective <tt>OperationSet</tt> instance
*/
protected Map<String, OperationSet> getLargestSupportedOperationSet(
ArrayList<String> fullJids)
{
Map<String, OperationSet> supportedOperationSets =
new HashMap<String, OperationSet>();
if (fullJids!=null)
{
for (String fullJid : fullJids)
{
Map<String, OperationSet> newSupportedOperationSets=
getSupportedOperationSets(fullJid, true);
if (newSupportedOperationSets.size()>
supportedOperationSets.size())
{
supportedOperationSets = newSupportedOperationSets;
}
}
}
return supportedOperationSets;
}
/**
* Gets the <tt>OperationSet</tt> corresponding to the specified
* <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the
* returned value is non-<tt>null</tt>, it indicates that the
* <tt>Contact</tt> is considered by the associated protocol provider to
* possess the <tt>opsetClass</tt> capability. Otherwise, the associated
* protocol provider considers <tt>contact</tt> to not have the
* <tt>opsetClass</tt> capability.
*
* @param <U> the type extending <tt>OperationSet</tt> for which the
* specified <tt>contact</tt> is to be checked whether it possesses it as a
* capability
* @param jid the Jabber id for which we're checking supported operation
* sets
* @param opsetClass the <tt>OperationSet</tt> <tt>Class</tt> for which the
* specified <tt>contact</tt> is to be checked whether it possesses it as a
* capability
* @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise,
* <tt>false</tt>
* @return the <tt>OperationSet</tt> corresponding to the specified
* <tt>opsetClass</tt> which is considered by the associated protocol
* provider to be possessed as a capability by the specified
* <tt>contact</tt>; otherwise, <tt>null</tt>
* @see AbstractOperationSetContactCapabilities#getOperationSet(Contact,
* Class)
*/
private <U extends OperationSet> U getOperationSet(String jid,
Class<U> opsetClass,
boolean online)
{
U opset = parentProvider.getOperationSet(opsetClass);
if (opset == null)
return null;
/*
* If the specified contact is offline, don't query its features (they
* should fail anyway).
*/
if (!online)
return OFFLINE_OPERATION_SETS.contains(opsetClass) ? opset : null;
/*
* If we know the features required for the support of opsetClass, check
* whether the contact supports them. Otherwise, presume the contact
* possesses the opsetClass capability in light of the fact that we miss
* any knowledge of the opsetClass whatsoever.
*/
if (OPERATION_SETS_TO_FEATURES.containsKey(opsetClass))
{
String[] features = OPERATION_SETS_TO_FEATURES.get(opsetClass);
/*
* Either we've completely disabled the opsetClass capability by
* mapping it to the null list of features or we've mapped it to an
* actual list of features which are to be checked whether the
* contact supports them.
*/
if ((features == null)
|| ((features.length != 0)
&& !parentProvider.isFeatureListSupported(
jid,
features)))
{
opset = null;
}
}
return opset;
}
/**
* Sets the <tt>EntityCapsManager</tt> which is associated with the
* <tt>discoveryManager</tt> of {@link #parentProvider}.
*
* @param capsManager the <tt>EntityCapsManager</tt> which is associated
* with the <tt>discoveryManager</tt> of {@link #parentProvider}
*/
private void setCapsManager(EntityCapsManager capsManager)
{
if (this.capsManager != capsManager)
{
if (this.capsManager != null)
this.capsManager.removeUserCapsNodeListener(this);
this.capsManager = capsManager;
if (this.capsManager != null)
this.capsManager.addUserCapsNodeListener(this);
}
}
/**
* Sets the <tt>ScServiceDiscoveryManager</tt> which is the
* <tt>discoveryManager</tt> of {@link #parentProvider}.
*
* @param discoveryManager the <tt>ScServiceDiscoveryManager</tt> which is
* the <tt>discoveryManager</tt> of {@link #parentProvider}
*/
void setDiscoveryManager(ScServiceDiscoveryManager discoveryManager)
{
setCapsManager(
(discoveryManager == null)
? null
: discoveryManager.getCapsManager());
}
/**
* Notifies this listener that an <tt>EntityCapsManager</tt> has added a
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
* @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user is currently online
* @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean)
*/
public void userCapsNodeAdded(String user, ArrayList<String> fullJids,
String node, boolean online)
{
/*
* It doesn't matter to us whether a caps node has been added or removed
* for the specified user because we report all changes.
*/
userCapsNodeChanged(user, fullJids, node, online);
}
/**
* Notifies this listener that an <tt>EntityCapsManager</tt> has removed a
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
* @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user is currently online
* @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean)
*/
public void userCapsNodeRemoved(String user, ArrayList<String> fullJids,
String node, boolean online)
{
/*
* It doesn't matter to us whether a caps node has been added or removed
* for the specified user because we report all changes.
*/
userCapsNodeChanged(user, fullJids, node, online);
}
/**
* Notifies this listener that an <tt>EntityCapsManager</tt> has changed a
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
* @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the given user is online
*/
public void userCapsNodeChanged(String user, ArrayList<String> fullJids,
String node, boolean online)
{
OperationSetPresence opsetPresence
= parentProvider.getOperationSet(OperationSetPresence.class);
if (opsetPresence != null) {
if(JabberActivator.getConfigurationService()
.getBoolean(
PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES,
USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT)
&& !fullJids.isEmpty())
{
String bareJid = StringUtils.parseBareAddress(user);
Contact contact = opsetPresence.findContactByID(bareJid);
if (contact != null)
{
fireContactCapabilitiesEvent(
contact,
ContactCapabilitiesEvent.
SUPPORTED_OPERATION_SETS_CHANGED,
getLargestSupportedOperationSet(fullJids));
}
}
else
{
String jid = StringUtils.parseBareAddress(user);
Contact contact = opsetPresence.findContactByID(jid);
// If the contact isn't null and is online we try to discover
// the new set of operation sets and to notify interested
// parties. Otherwise we ignore the event.
if (contact != null)
{
if(online)
{
// when going online we have received a presence
// and make sure we discover this particular jid
// for getSupportedOperationSets
fireContactCapabilitiesEvent(
contact,
ContactCapabilitiesEvent.
SUPPORTED_OPERATION_SETS_CHANGED,
getSupportedOperationSets(user,
online));
}
else
{
// when offline, we use the contact, and selecting
// the most connected jid
// for getSupportedOperationSets
fireContactCapabilitiesEvent(
contact,
ContactCapabilitiesEvent.
SUPPORTED_OPERATION_SETS_CHANGED,
getSupportedOperationSets(contact));
}
}
}
}
}
/**
* Removes the capabilities when the user goes offline.
*
* @param evt the <tt>ContactPresenceStatusChangeEvent</tt> that notified
* us
*/
public void contactPresenceStatusChanged(
ContactPresenceStatusChangeEvent evt)
{
// If the user goes offline we ensure to remove the caps node.
if (capsManager != null
&& evt.getNewStatus().getStatus() < PresenceStatus.ONLINE_THRESHOLD
&& !evt.isResourceChanged())
{
capsManager.removeContactCapsNode(evt.getSourceContact());
}
}
/**
* Fires event that contact capabilities has changed.
* @param user the user to search for its contact.
* @param fullJids a list of all resources of the user (full JIDs)
*/
public void fireContactCapabilitiesChanged(String user,
ArrayList<String> fullJids)
{
if(!JabberActivator.getConfigurationService()
.getBoolean(
PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES,
USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT)
|| fullJids.isEmpty())
{
OperationSetPresence opsetPresence
= parentProvider.getOperationSet(OperationSetPresence.class);
if (opsetPresence != null)
{
String userID = StringUtils.parseBareAddress(user);
Contact contact = opsetPresence.findContactByID(userID);
// this called by received discovery info for particular jid
// so we use its online and opsets for this particular jid
boolean online = false;
Presence presence = parentProvider.getConnection().getRoster()
.getPresence(user);
if(presence != null)
online = presence.isAvailable();
if(contact != null)
{
fireContactCapabilitiesEvent(
contact,
ContactCapabilitiesEvent.
SUPPORTED_OPERATION_SETS_CHANGED,
getSupportedOperationSets(user, online));
}
}
}
else
{
OperationSetPresence opsetPresence
= parentProvider.getOperationSet(OperationSetPresence.class);
if (opsetPresence != null)
{
String bareJid = StringUtils.parseBareAddress(user);
Contact contact = opsetPresence.findContactByID(bareJid);
if(contact != null)
{
fireContactCapabilitiesEvent(
contact,
ContactCapabilitiesEvent.
SUPPORTED_OPERATION_SETS_CHANGED,
getLargestSupportedOperationSet(fullJids));
}
}
}
}
}