package org.limewire.friend.impl.address; import java.io.IOException; import java.net.URI; import java.util.Map.Entry; import org.limewire.friend.api.Friend; import org.limewire.friend.api.FriendConnectionEvent; import org.limewire.friend.api.FriendPresence; import org.limewire.friend.api.feature.AddressFeature; import org.limewire.friend.api.feature.AuthTokenFeature; import org.limewire.friend.api.feature.ConnectBackRequestFeature; import org.limewire.friend.api.feature.Feature; import org.limewire.friend.api.feature.FeatureEvent; import org.limewire.friend.api.feature.FeatureEvent.Type; import org.limewire.io.Address; import org.limewire.listener.EventBean; import org.limewire.listener.EventBroadcaster; import org.limewire.listener.EventListener; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.net.ConnectivityChangeEvent; import org.limewire.net.SocketsManager; import org.limewire.net.address.AddressResolutionObserver; import org.limewire.net.address.AddressResolver; import org.limewire.net.address.FirewalledAddress; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Resolves addresses of type {@link FriendAddress} by looking up the full jabber id * including resource in the logged in users. */ @Singleton public class FriendAddressResolver implements AddressResolver { private final static Log LOG = LogFactory.getLog(FriendAddressResolver.class, LOGGING_CATEGORY); private final EventBean<FriendConnectionEvent> connectionEventBean; private final EventBroadcaster<ConnectivityChangeEvent> connectivityEventBroadcaster; private final ConnectivyFeatureListener connectivyFeatureListener = new ConnectivyFeatureListener(); private final SocketsManager socketsManager; private final FriendAddressRegistry addressRegistry; @Inject public FriendAddressResolver(EventBean<FriendConnectionEvent> connectionEventBean, EventBroadcaster<ConnectivityChangeEvent> connectivityEventBroadcaster, SocketsManager socketsManager, FriendAddressRegistry addressRegistry) { this.connectionEventBean = connectionEventBean; this.connectivityEventBroadcaster = connectivityEventBroadcaster; this.socketsManager = socketsManager; this.addressRegistry = addressRegistry; } @Inject void register(SocketsManager socketsManager, ListenerSupport<FeatureEvent> featureSupport) { socketsManager.registerResolver(this); featureSupport.addListener(connectivyFeatureListener); } @Override public boolean canResolve(Address address) { if (address instanceof FriendAddress) { FriendAddress friendIdAddress = (FriendAddress)address; FriendPresence presence = getPresence(friendIdAddress); if (presence == null) { LOG.debugf("can not resolve remote address {0} because no presence was found}", friendIdAddress); return false; } LOG.debugf("can resolve remote address {0}", address); return true; } LOG.debugf("can not resolve remote address {0}", address); return false; } /** * Returns the friend presence belonging to an <code>address</code>. * * @return null if not presence is found for the address, i.e. the user * is not online for example */ public FriendPresence getPresence(FriendAddress address) { String id = address.getId(); FriendConnectionEvent connection = connectionEventBean.getLastEvent(); if(connection == null || connection.getType() != FriendConnectionEvent.Type.CONNECTED) return null; Friend friend = connection.getSource().getFriend(id); if(friend != null) { for(Entry<String, FriendPresence> entry : friend.getPresences().entrySet()) { FriendPresence resolvedPresence = getMatchingPresence(address, entry.getKey(), entry.getValue()); if(resolvedPresence != null) { return resolvedPresence; } } } return null; } /** * Returns presence if presence is {@link FriendPresence} and the resource * id matches the one in <code>address</code> and the address is available in * the presence. * <p> * Also ensures that auth-token and address feature are set. */ private FriendPresence getMatchingPresence(FriendAddress friendAddress, String resourceId, FriendPresence presence) { if (friendAddress.equals(new FriendAddress(resourceId))) { // only return address actual address is not null and auth-token is // available too, otherwise the address is worthless still Address address = addressRegistry.get(friendAddress); Feature authTokenFeature = presence.getFeature(AuthTokenFeature.ID); if(address != null && authTokenFeature != null) { return presence; } LOG.debugf("address is {0}, token features is {1}", address, authTokenFeature); } return null; } @Override public <T extends AddressResolutionObserver> T resolve(Address address, T observer) { LOG.debugf("resolving: {0}", address); FriendAddress friendAddress = (FriendAddress)address; FriendPresence resolvedPresence = getPresence(friendAddress); if (resolvedPresence == null) { LOG.debugf("{0} could not be resolved", address); observer.handleIOException(new IOException("Could not be resolved")); } else { Address resolvedAddress = addressRegistry.get(friendAddress); // race condition, address could have been nulled in the mean time, // although it was checked in getPresence() if (resolvedAddress == null) { LOG.debugf("could not resolve {0}, not in registry {1}", address, addressRegistry); observer.handleIOException(new IOException("could not be resolved, no address")); } else if (resolvedAddress instanceof FirewalledAddress) { // if it's a firewalled address, see if sockets manager can resolve if further, i.e. // if SameNATResolver can take care of it if (socketsManager.canResolve(resolvedAddress)) { LOG.debugf("can be same nat resolved {0}", address); socketsManager.resolve(resolvedAddress, observer); } else if (resolvedPresence.hasFeatures(ConnectBackRequestFeature.ID)) { // else make it an xmpp firewalled address, so connect requests can be sent over xmpp resolvedAddress = new FriendFirewalledAddress(friendAddress, (FirewalledAddress)resolvedAddress); LOG.debugf("resolved {0} as xmpp firewalled address {1}", address, resolvedAddress); observer.resolved(resolvedAddress); } else { LOG.debugf("resolved {0} as firewalled address {1}", address, resolvedAddress); observer.resolved(resolvedAddress); } } else { LOG.debugf("resolved {0} as non-firewalled address {1}", address, resolvedAddress); observer.resolved(resolvedAddress); } } return observer; } private class ConnectivyFeatureListener implements EventListener<FeatureEvent> { @Override public void handleEvent(FeatureEvent event) { if (event.getType() != Type.ADDED) { return; } URI id = event.getData().getID(); if (id.equals(AuthTokenFeature.ID) || id.equals(AddressFeature.ID)) { FriendPresence presence = event.getSource(); if (presence.hasFeatures(AuthTokenFeature.ID, AddressFeature.ID)) { LOG.debugf("presence with address and auth-token became available: {0}", presence.getPresenceId()); connectivityEventBroadcaster.broadcast(new ConnectivityChangeEvent()); } } } } }