package org.limewire.net;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.limewire.io.Address;
import org.limewire.io.NetworkUtils;
import org.limewire.io.SimpleNetworkInstanceUtils;
import org.limewire.io.AddressConnectingLoggingCategory;
import org.limewire.listener.EventBroadcaster;
import org.limewire.listener.EventListener;
import org.limewire.listener.EventMulticaster;
import org.limewire.listener.EventMulticasterImpl;
import org.limewire.listener.ListenerSupport;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.net.address.AddressConnector;
import org.limewire.net.address.AddressResolutionObserver;
import org.limewire.net.address.AddressResolver;
import org.limewire.nio.NBSocket;
import org.limewire.nio.NBSocketFactory;
import org.limewire.nio.observer.ConnectObserver;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/** Factory for creating Sockets. */
@Singleton
public class SocketsManagerImpl implements SocketsManager, EventBroadcaster<ConnectivityChangeEvent>, ListenerSupport<ConnectivityChangeEvent> {
private final static Log LOG = LogFactory.getLog(SocketsManagerImpl.class, AddressConnectingLoggingCategory.CATEGORY);
private final SocketController socketController;
private final List<AddressResolver> addressResolvers = new CopyOnWriteArrayList<AddressResolver>();
private final List<AddressConnector> addressConnectors = new CopyOnWriteArrayList<AddressConnector>();
private final EventMulticaster<ConnectivityChangeEvent> connectivityEventMulticaster = new EventMulticasterImpl<ConnectivityChangeEvent>();
public SocketsManagerImpl() {
this(new SimpleSocketController(new ProxyManagerImpl(new EmptyProxySettings(), new SimpleNetworkInstanceUtils()), new EmptySocketBindingSettings()));
}
@Inject
public SocketsManagerImpl(SocketController socketController) {
this.socketController = socketController;
}
public Socket create(ConnectType type) throws IOException {
return type.getFactory().createSocket();
}
public Socket connect(NBSocket socket, InetSocketAddress localAddr, InetSocketAddress addr, int timeout, ConnectType type) throws IOException {
return connect(socket, localAddr, addr, timeout, null, type);
}
public Socket connect(InetSocketAddress addr, int timeout) throws IOException {
return connect(addr, timeout, ConnectType.PLAIN);
}
public Socket connect(InetSocketAddress addr, int timeout, ConnectType type) throws IOException {
return connect(addr, timeout, null, type);
}
public Socket connect(InetSocketAddress addr, int timeout, ConnectObserver observer) throws IOException {
return connect(addr, timeout, observer, ConnectType.PLAIN);
}
public Socket connect(InetSocketAddress addr, int timeout, ConnectObserver observer, ConnectType type) throws IOException {
return connect(null, null, addr, timeout, observer, type);
}
public Socket connect(final NBSocket socket, InetSocketAddress localAddr, InetSocketAddress addr, int timeout, ConnectObserver observer, ConnectType type) throws IOException {
if(!NetworkUtils.isValidPort(addr.getPort()))
throw new IllegalArgumentException("port out of range: "+addr.getPort());
if(addr.isUnresolved())
throw new IOException("address must be resolved!");
if(socket == null) {
return socketController.connect(type.getFactory(), addr, null, timeout, observer);
} else {
NBSocketFactory factory = new NBSocketFactory() {
@Override
public NBSocket createSocket() throws IOException {
return socket;
}
@Override
public NBSocket createSocket(String host, int port) throws IOException,
UnknownHostException {
throw new UnsupportedOperationException();
}
@Override
public NBSocket createSocket(InetAddress host, int port) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public NBSocket createSocket(String host, int port, InetAddress localHost,
int localPort) throws IOException, UnknownHostException {
throw new UnsupportedOperationException();
}
@Override
public NBSocket createSocket(InetAddress address, int port,
InetAddress localAddress, int localPort) throws IOException {
throw new UnsupportedOperationException();
}
};
return socketController.connect(factory, addr, localAddr, timeout, observer);
}
}
public boolean removeConnectObserver(ConnectObserver observer) {
return socketController.removeConnectObserver(observer);
}
public int getNumAllowedSockets() {
return socketController.getNumAllowedSockets();
}
public int getNumWaitingSockets() {
return socketController.getNumWaitingSockets();
}
private AddressResolver getResolver(Address address) {
for (AddressResolver resolver : addressResolvers) {
if (resolver.canResolve(address)) {
LOG.debugf("found resolver: {0} for: {1}", resolver, address);
return resolver;
}
}
return null;
}
private AddressConnector getConnector(Address address) {
for (AddressConnector connector : addressConnectors) {
if (connector.canConnect(address)) {
LOG.debugf("found connector: {0} for: {1}", connector, address);
return connector;
}
}
return null;
}
@Override
public boolean canConnect(Address address) {
return getConnector(address) != null;
}
@Override
public boolean canResolve(Address address) {
return getResolver(address) != null;
}
@Override
public <T extends ConnectObserver> T connect(Address address, final T observer) {
// feel free to rework this logic with more use cases that don't fit the model
// for example we're only doing one cycle of address resolution, might have to
// be done iteratively if addresses are resolved to address that need more resolution
if (address == null) {
throw new NullPointerException("address must not be null");
}
if (canResolve(address)) {
LOG.debugf("trying to resolve for connect: {0}", address);
resolve(address, new AddressResolutionObserver() {
@Override
public void resolved(Address address) {
connectUnresolved(address, observer);
}
@Override
public void handleIOException(IOException iox) {
observer.handleIOException(iox);
}
@Override
public void shutdown() {
// observer.shutdown();
}
});
} else {
LOG.debugf("trying to connect unresolved: {0}", address);
connectUnresolved(address, observer);
}
return observer;
}
private void connectUnresolved(Address address, ConnectObserver observer) {
AddressConnector connector = getConnector(address);
if (connector != null) {
connector.connect(address, observer);
} else {
observer.handleIOException(new ConnectException("no connector ready to connect to: " + address));
observer.shutdown();
}
}
@Override
public <T extends AddressResolutionObserver> T resolve(final Address address, final T observer) {
// feel free to rework this logic with more use cases that don't fit the model
if (address == null) {
throw new NullPointerException("address must not be null");
}
AddressResolver resolver = getResolver(address);
if (resolver != null) {
resolver.resolve(address, new AddressResolutionObserver() {
@Override
public void resolved(Address resolvedAddress) {
LOG.debugf("resolved {0} to {1}", address, resolvedAddress);
if (canResolve(resolvedAddress)) {
resolve(resolvedAddress, this);
} else {
observer.resolved(resolvedAddress);
}
}
@Override
public void handleIOException(IOException iox) {
observer.handleIOException(iox);
}
@Override
public void shutdown() {
observer.shutdown();
}
});
} else {
LOG.debugf("not resolver found for: {0}", address);
observer.handleIOException(new IOException(address + " cannot be resolved"));
}
return observer;
}
@Override
public void registerConnector(AddressConnector connector) {
addressConnectors.add(connector);
}
@Override
public void registerResolver(AddressResolver resolver) {
addressResolvers.add(resolver);
}
@Override
public void addListener(EventListener<ConnectivityChangeEvent> listener) {
connectivityEventMulticaster.addListener(listener);
}
@Override
public boolean removeListener(EventListener<ConnectivityChangeEvent> listener) {
return connectivityEventMulticaster.removeListener(listener);
}
@Override
public void broadcast(ConnectivityChangeEvent event) {
connectivityEventMulticaster.broadcast(event);
}
}