/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.net;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import de.dal33t.powerfolder.ConfigurationEntry;
import de.dal33t.powerfolder.Constants;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.Member;
import de.dal33t.powerfolder.PFComponent;
import de.dal33t.powerfolder.light.MemberInfo;
import de.dal33t.powerfolder.util.StringUtils;
import de.dal33t.powerfolder.util.net.NetworkUtil;
import de.dal33t.powerfolder.util.net.UDTSocket;
/**
* The default factory which creates <code>ConnectionHandler</code>s. Is capable
* of connecting to a remote node by {@link #tryToConnect(MemberInfo)} or to a
* remote address by {@link #tryToConnect(InetSocketAddress)}.
* <p>
* The connection attempt by {@link #tryToConnect(MemberInfo)} should always be
* prefered since it include the logical peer address (<code>MemberInfo</code>)
* of the remote node. Fully relayed connections for exampled don't require a
* physical TCP address, but require that logical peer address.
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a>
* @version $Revision: 1.5 $
*/
public class ConnectionHandlerFactory extends PFComponent {
public ConnectionHandlerFactory(Controller controller) {
super(controller);
}
// Main connect methods ***************************************************
/**
* Tries establish a physical connection to that node.
* <p>
* Connection strategy when using this method:
* </p>
* A) Socket connection
* <p>
* B) Relayed connection
* <p>
* C) PRO only: HTTP tunneled connection
*
* @param remoteNode
* the node to reconnect to.
* @return a ready initializes connection handler.
* @throws ConnectionException
*/
public ConnectionHandler tryToConnect(MemberInfo remoteNode)
throws ConnectionException
{
boolean nullIP = remoteNode.getConnectAddress() == null
|| remoteNode.getConnectAddress().getAddress() == null
|| NetworkUtil
.isNullIP(remoteNode.getConnectAddress().getAddress());
if (!nullIP) {
try {
ConnectionHandler handler = tryToConnectTCP(remoteNode
.getConnectAddress());
return handler;
} catch (ConnectionException e) {
logFiner(e);
}
}
try {
if (useUDTConnections() && useRelayedTunneledConnection(remoteNode)
&& !nullIP)
{
ConnectionHandler handler = tryToConnectUDTRendezvous(remoteNode);
return handler;
}
} catch (ConnectionException e) {
logFiner(e);
}
try {
if (useRelayedConnections()
&& useRelayedTunneledConnection(remoteNode))
{
ConnectionHandler handler = tryToConnectRelayed(remoteNode);
return handler;
}
} catch (ConnectionException e) {
logFiner(e);
}
throw new ConnectionException("No further connection alternatives.");
}
/**
* Tries establish a physical connection to that node.
* <p>
* Connection strategy when using this method:
* </p>
* A) Socket connection
* <p>
* B) PRO only: HTTP tunneled connection
*
* @param remoteAddress
* the address to connect to
* @return a ready initializes connection handler.
* @throws ConnectionException
*/
public ConnectionHandler tryToConnect(InetSocketAddress remoteAddress)
throws ConnectionException
{
if (NetworkUtil.isNullIP(remoteAddress.getAddress())) {
throw new ConnectionException("Unable to connect to null IP: "
+ remoteAddress);
}
return tryToConnectTCP(remoteAddress);
}
// Factory methods ********************************************************
/**
* Creates a initialized connection handler for a socket based TCP/IP
* connection.
*
* @param socket
* the tcp/ip socket
* @return the connection handler for basic IO connection.
* @throws ConnectionException
*/
public ConnectionHandler createAndInitSocketConnectionHandler(Socket socket)
throws ConnectionException
{
ConnectionHandler conHan = new PlainSocketConnectionHandler(
getController(), socket);
try {
conHan.init();
} catch (ConnectionException e) {
conHan.shutdown();
throw e;
}
return conHan;
}
/**
* Constructs a new relayed connection handler with the given configuration.
* ConnectionHandler must not been initialized - That is done later.
*
* @param destination
* the destination node
* @param connectionId
* the unique connection id
* @param relay
* the relay to use
* @return the connection handler.
*/
public AbstractRelayedConnectionHandler createRelayedConnectionHandler(
MemberInfo destination, long connectionId, Member relay)
{
return new PlainRelayedConnectionHandler(getController(), destination,
connectionId, relay);
}
/**
* Creates an initialized connection handler for a UDT socket based on UDP
* connection.
*
* @param socket
* the UDT socket
* @return the connection handler for basic IO connection.
* @throws ConnectionException
*/
public AbstractUDTSocketConnectionHandler createAndInitUDTSocketConnectionHandler(
UDTSocket socket) throws ConnectionException
{
AbstractUDTSocketConnectionHandler conHan = new PlainUDTSocketConnectionHandler(
getController(), socket);
try {
conHan.init();
} catch (ConnectionException e) {
conHan.shutdown();
throw e;
}
return conHan;
}
// Connection layer specific connect methods ******************************
/**
* Tries establish a physical socket connection to that node.
*
* @param remoteAddress
* the address to connect to.
* @return a ready initializes connection handler.
* @throws ConnectionException
* if no connection is possible.
*/
protected ConnectionHandler tryToConnectTCP(InetSocketAddress remoteAddress)
throws ConnectionException
{
try {
Socket socket = new Socket();
String cfgBind = ConfigurationEntry.NET_BIND_ADDRESS
.getValue(getController());
if (!StringUtils.isEmpty(cfgBind)) {
socket.bind(new InetSocketAddress(cfgBind, 0));
}
socket.connect(remoteAddress, Constants.SOCKET_CONNECT_TIMEOUT);
NetworkUtil.setupSocket(socket, getController());
return createAndInitSocketConnectionHandler(socket);
} catch (IOException e) {
throw new ConnectionException("Unable to connect to "
+ remoteAddress + ": " + e.getMessage(), e);
}
}
/**
* Tries to establish a relayed connection to that remote node.
*
* @param remoteNode
* the node to connect to
* @return the ready-initialized connection handler
* @throws ConnectionException
* if no connection is possible.
*/
protected ConnectionHandler tryToConnectRelayed(MemberInfo remoteNode)
throws ConnectionException
{
if (isFiner()) {
logFiner("Trying relayed connection to " + remoteNode.nick);
}
return getController().getIOProvider().getRelayedConnectionManager()
.initRelayedConnectionHandler(remoteNode);
}
/**
* Tries to establish a UDT connection in rendezvous mode via relay.
*
* @param remoteNode
* the node to connect to
* @return the ready-initialized connection handler
* @throws ConnectionException
* if no connection is possible.
*/
protected ConnectionHandler tryToConnectUDTRendezvous(MemberInfo remoteNode)
throws ConnectionException
{
if (isFiner()) {
logFiner("Trying UDT socket connection to " + remoteNode.nick);
}
return getController().getIOProvider().getUDTSocketConnectionManager()
.initRendezvousUDTConnectionHandler(remoteNode);
}
// Internal helper ********************************************************
protected boolean useRelayedConnections() {
return !getController().isLanOnly()
&& ConfigurationEntry.RELAYED_CONNECTIONS_ENABLED
.getValueBoolean(getController())
&& !getController().getIOProvider().getRelayedConnectionManager()
.isRelay(getController().getMySelf().getInfo());
}
protected boolean useUDTConnections() {
return UDTSocket.isSupported()
&& !getController().isLanOnly()
&& ConfigurationEntry.UDT_CONNECTIONS_ENABLED
.getValueBoolean(getController())
&& !getController().getIOProvider().getRelayedConnectionManager()
.isRelay(getController().getMySelf().getInfo());
}
protected boolean isOnLAN(MemberInfo node) {
InetAddress adr = node.getConnectAddress() != null
&& node.getConnectAddress().getAddress() != null ? node
.getConnectAddress().getAddress() : null;
if (adr == null) {
return false;
}
return getController().getNodeManager().isOnLANorConfiguredOnLAN(adr);
}
protected boolean useRelayedTunneledConnection(MemberInfo node) {
boolean onLan = isOnLAN(node);
if (!onLan) {
// Always try for Internet connection.
return true;
} else {
// Only if really wanted.
return ConfigurationEntry.NET_USE_RELAY_TUNNEL_ON_LAN
.getValueBoolean(getController());
}
}
protected boolean useRelayedTunneledConnection(InetAddress adr) {
boolean onLan = getController().getNodeManager()
.isOnLANorConfiguredOnLAN(adr);
if (!onLan) {
// Always try for Internet connection.
return true;
} else {
// Only if really wanted.
return ConfigurationEntry.NET_USE_RELAY_TUNNEL_ON_LAN
.getValueBoolean(getController());
}
}
public ConnectionQuality getConnectionQuality() {
int good = 0;
int medium = 0;
int poor = 0;
for (Member node : getController().getNodeManager().getConnectedNodes())
{
ConnectionHandler peer = node.getPeer();
ConnectionQuality qual = peer != null
? peer.getConnectionQuality()
: null;
if (qual == null) {
continue;
}
if (qual.equals(ConnectionQuality.GOOD)) {
good++;
} else if (qual.equals(ConnectionQuality.MEDIUM)) {
medium++;
} else if (qual.equals(ConnectionQuality.POOR)) {
poor++;
}
}
if (isFiner()) {
logFiner("Connections ==> good: " + good + ", medium: " + medium
+ ", poor: " + poor);
}
if (good == 0 && medium == 0 && poor <= 1) {
return null;
}
if (good > poor && good > medium) {
return ConnectionQuality.GOOD;
} else if (medium > poor) {
return ConnectionQuality.MEDIUM;
}
return ConnectionQuality.POOR;
}
}