/* * 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)); } } } } }