package org.limewire.xmpp.client.impl.messages.discoinfo; import java.net.URI; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.ServiceDiscoveryManager; import org.jivesoftware.smackx.packet.DiscoverInfo; import org.limewire.friend.api.Friend; import org.limewire.friend.api.FriendConnection; import org.limewire.friend.api.FriendConnectionEvent; import org.limewire.friend.api.FriendPresence; import org.limewire.friend.api.FriendPresenceEvent; import org.limewire.friend.api.feature.FeatureRegistry; import org.limewire.listener.BlockingEvent; import org.limewire.listener.EventListener; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.xmpp.client.impl.XMPPFriendConnectionImpl; /** * sends disco info messages (http://jabber.org/protocol/disco#info) to newly available * presences and then calls the appropriate FeatureInitializer for each of the * features that come back in the response. */ public class DiscoInfoListener implements PacketListener { private static final Log LOG = LogFactory.getLog(DiscoInfoListener.class); private final FriendConnection connection; private final org.jivesoftware.smack.XMPPConnection smackConnection; private final XMPPConnectionListener connectionListener = new XMPPConnectionListener(); private ListenerSupport<FriendConnectionEvent> connectionSupport; private ListenerSupport<FriendPresenceEvent> friendPresenceSupport; private final FriendPresenceListener friendPresenceListener = new FriendPresenceListener(); private final PacketFilter packetFilter = new DiscoPacketFilter(); private final FeatureRegistry featureRegistry; public DiscoInfoListener(FriendConnection connection, org.jivesoftware.smack.XMPPConnection smackConnection, FeatureRegistry featureRegistry) { this.connection = connection; this.smackConnection = smackConnection; this.featureRegistry = featureRegistry; } public void addListeners(ListenerSupport<FriendConnectionEvent> connectionSupport, ListenerSupport<FriendPresenceEvent> friendPresenceSupport) { this.connectionSupport = connectionSupport; this.friendPresenceSupport = friendPresenceSupport; connectionSupport.addListener(connectionListener); friendPresenceSupport.addListener(friendPresenceListener); smackConnection.addPacketListener(this, packetFilter); } @Override public void processPacket(Packet packet) { DiscoverInfo discoverInfo = (DiscoverInfo) packet; String from = discoverInfo.getFrom(); if (from == null) { if (LOG.isDebugEnabled()) { LOG.debugf("null from field: {0}", discoverInfo.getChildElementXML()); } return; } FriendPresence friendPresence = matchValidPresence(from); if (friendPresence == null && !isForThisConnection(from)) { LOG.debugf("no presence found for and not for this connection: {0}", from); return; } String featureInitializer = friendPresence != null ? friendPresence.getPresenceId() : from; for (URI uri : featureRegistry.getAllFeatureUris()) { if (discoverInfo.containsFeature(uri.toASCIIString())) { LOG.debugf("initializing feature {0} for {1}", uri.toASCIIString(), featureInitializer); featureRegistry.get(uri).initializeFeature(friendPresence); } } } public void cleanup() { if (connectionListener != null) { connectionSupport.removeListener(connectionListener); } if (friendPresenceSupport != null) { friendPresenceSupport.removeListener(friendPresenceListener); } smackConnection.removePacketListener(this); } /** * Blockingly discovers features of an xmpp entity. * * @param entityName name of entity (can be anything, such as a * presence id, an xmpp server name, etc) */ private void discoverFeatures(String entityName) { try { ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(smackConnection); // check for null due to race condition between whoever is doing feature discovery // and smack connection shutting down. if shut down, no features worth discovering. if (serviceDiscoveryManager != null) { LOG.debugf("discovering presence: {0}", entityName); serviceDiscoveryManager.discoverInfo(entityName); } else { LOG.debug("no service discovery manager"); } } catch (org.jivesoftware.smack.XMPPException exception) { LOG.info(exception.getMessage(), exception); if (exception.getXMPPError() != null && !exception.getXMPPError().getCondition(). equals(XMPPError.Condition.feature_not_implemented.toString())) { } } } private boolean isForThisConnection(String from) { return connection.getConfiguration().getServiceName().equals(from); } /** * @param from address (e.g. loginName@serviceName.com/resourceInfo) * @return the intended presence of the announced feature based on * what is in the disco info packet. * <p/> * Returns NULL if there is no presence for the announced feature */ private FriendPresence matchValidPresence(String from) { // does the from string match a presence Friend friend = connection.getFriend(StringUtils.parseBareAddress(from)); if (friend != null) { FriendPresence presence = friend.getPresences().get(from); if (presence != null) { return presence; } } // gets here if packet contains a from string indicating // a presence we don't know about. Or there is an unknown problem. return null; } // listen for new presences in order to discover presence features private class FriendPresenceListener implements EventListener<FriendPresenceEvent> { @BlockingEvent(queueName = "feature discovery") @Override public void handleEvent(final FriendPresenceEvent event) { if (event.getType() == FriendPresenceEvent.Type.ADDED) { discoverFeatures(event.getData().getPresenceId()); } } } // listen for new connections in order to discover server features private class XMPPConnectionListener implements EventListener<FriendConnectionEvent> { @BlockingEvent(queueName = "feature discovery") @Override public void handleEvent(FriendConnectionEvent event) { if (!(event.getSource() instanceof XMPPFriendConnectionImpl)) { return; } if (event.getType() == FriendConnectionEvent.Type.CONNECTED) { discoverFeatures(connection.getConfiguration().getServiceName()); } } } private static class DiscoPacketFilter implements PacketFilter { @Override public boolean accept(Packet packet) { return packet instanceof DiscoverInfo && (((DiscoverInfo) packet).getType() == IQ.Type.SET || ((DiscoverInfo) packet).getType() == IQ.Type.RESULT); } } }