package thaw.fcp;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Observable;
import thaw.core.Logger;
/**
* This object manages directly the socket attached to the node.
* After being instanciated, you should commit it to the FCPQueryManager, and then
* commit the FCPQueryManager to the FCPQueueManager.
* Call observer when connected / disconnected.<br/>
* WARNING: This FCP implementation doesn't guarantee that messages are sent in the same order than initally put
* if the lock on writting is not set !<br/>
*/
public class FCPConnection extends Observable {
/**
* If == true, then will print on stdout
* all fcp input / output.
*/
private final static boolean DEBUG_MODE = true;
private final static int MAX_RECV = 1024;
/* global to avoid each time free() / malloc() */
private final byte[] recvBytes = new byte[FCPConnection.MAX_RECV];
private FCPBufferedStream bufferedOut = null;
private int maxUploadSpeed = 0;
private String nodeAddress = null;
private int port = 0;
private Socket socket = null;
private InputStream in = null;
private OutputStream out = null;
private BufferedInputStream reader = null;
private long rawBytesWaiting = 0;
private int writersWaiting;
private Object monitor;
private boolean duplicationAllowed = true;
private boolean localSocket = false;
private boolean autoDownload = true;
private FCPClientHello clientHello;
/**
* Don't connect. Call connect() for that.
* @param maxUploadSpeed in KB: -1 means no limit
* @param duplicationAllowed FCPClientGet and FCPClientPut will be allowed to
* open a separate socket to transfer the files
* @param autoDownload If !localSocket and if autoDownload, then files are automatically downloaded
* when the transfer ends
*/
public FCPConnection(final String nodeAddress,
final int port,
int maxUploadSpeed,
boolean duplicationAllowed,
final boolean localSocket,
final boolean autoDownload)
{
if (localSocket)
duplicationAllowed = false;
if(FCPConnection.DEBUG_MODE) {
Logger.notice(this, "DEBUG_MODE ACTIVATED");
}
monitor = new Object();
maxUploadSpeed = -1;
setNodeAddress(nodeAddress);
setNodePort(port);
setMaxUploadSpeed(maxUploadSpeed);
setDuplicationAllowed(duplicationAllowed);
setLocalSocket(localSocket);
setAutoDownload(autoDownload);
writersWaiting = 0;
}
public void setNodeAddress(final String nodeAddress) {
this.nodeAddress = nodeAddress;
}
public void setNodePort(final int port) {
this.port = port;
}
public void setMaxUploadSpeed(final int max) {
maxUploadSpeed = max;
}
public void setDuplicationAllowed(final boolean allowed) {
duplicationAllowed = allowed;
}
public void setLocalSocket(final boolean local) {
localSocket = local;
}
public void setAutoDownload(final boolean autoDownload) {
this.autoDownload = autoDownload;
}
public boolean getAutoDownload() {
return autoDownload;
}
public boolean isLocalSocket() {
return localSocket;
}
public void disconnect() {
try {
if(isConnected())
socket.close();
else {
Logger.info(this, "Disconnect(): Already disconnected.");
}
synchronized(monitor) {
monitor.notifyAll();
}
} catch(final java.io.IOException e) {
Logger.warning(this, "Unable to close cleanly the connection : "
+e.toString() +" ; "+e.getMessage());
}
socket = null;
in = null;
out = null;
if(bufferedOut != null) {
bufferedOut.stopSender();
bufferedOut = null;
}
setChanged();
this.notifyObservers();
}
/**
* If already connected, disconnect before connecting.
* @return true if successful
*/
public boolean connect() {
if((nodeAddress == null) || (port == 0)) {
Logger.warning(this, "Address or port not defined ! Unable to connect");
return false;
}
Logger.info(this, "Connection to "+nodeAddress+":"+ Integer.toString(port) +"...");
if((socket != null) && !socket.isClosed())
disconnect();
try {
socket = new Socket(nodeAddress, port);
} catch(final java.net.UnknownHostException e) {
Logger.error(this, "Error while trying to connect to "+nodeAddress+":"+port+" : "+
e.toString());
socket = null;
return false;
} catch(final java.io.IOException e) {
Logger.error(this, "Error while trying to connect to "+nodeAddress+":"+port+" : "+
e.toString() + " ; "+e.getMessage());
socket = null;
return false;
}
if(!socket.isConnected()) {
Logger.warning(this, "Unable to connect, but no exception ?!");
Logger.warning(this, "Will try to continue ...");
}
try {
in = socket.getInputStream();
out = socket.getOutputStream();
} catch(final java.io.IOException e) {
Logger.error(this, "Socket and connection established, but unable to get "+
"in/output streams ?! : "+e.toString()+ " ; "+e.getMessage() );
return false;
}
reader = new BufferedInputStream(in);
bufferedOut = new FCPBufferedStream(this, maxUploadSpeed);
bufferedOut.startSender();
rawBytesWaiting = 0;
writersWaiting = 0;
Logger.info(this, "Connected");
setChanged();
this.notifyObservers();
return true;
}
public boolean isOutputBufferEmpty() {
return bufferedOut.isOutputBufferEmpty();
}
public boolean isConnected() {
if(socket == null)
return false;
else
return socket.isConnected();
}
/**
* Doesn't check the lock state ! You have to manage it yourself.
*/
public synchronized boolean rawWrite(final byte[] data) {
if(bufferedOut != null)
return bufferedOut.write(data);
else {
Logger.notice(this, "rawWrite(), bufferedOut == null ? Socket closed ?");
disconnect();
return false;
}
}
/**
* Should be call by FCPBufferedStream.
*/
protected synchronized boolean realRawWrite(final byte[] data) {
if((out != null) && (socket != null) && socket.isConnected()) {
try {
out.write(data);
out.flush();
} catch(final java.io.IOException e) {
Logger.warning(this, "Unable to write() on the socket ?! : "
+ e.toString()+ " ; "+e.getMessage());
disconnect();
return false;
}
} else {
Logger.notice(this, "Cannot write if disconnected !");
if (out == null)
Logger.notice(this, "^ no output stream ^");
if (socket == null)
Logger.notice(this, "^ no socket ^");
else if (!socket.isConnected())
Logger.notice(this, "^ socket but not connected ^");
return false;
}
return true;
}
public void addToWriterQueue() {
synchronized(monitor) {
writersWaiting++;
if (writersWaiting > 1) {
try {
monitor.wait();
} catch(final java.lang.InterruptedException e) {
Logger.warning(this, "Interrupted while waiting ?!");
}
}
return;
}
}
public void removeFromWriterQueue() {
synchronized(monitor) {
writersWaiting--;
if (writersWaiting < 0) {
Logger.warning(this, "Negative number of writers ?!");
writersWaiting = 0;
}
monitor.notify();
}
}
public boolean isWriting() {
if( !isConnected() ) {
return false;
}
return (writersWaiting > 0);
}
public boolean write(final String toWrite) {
return this.write(toWrite, true);
}
public boolean write(final String toWrite, final boolean checkLock) {
if (checkLock) {
addToWriterQueue();
}
if (DEBUG_MODE) {
Logger.debug(this, "Thaw >>> Node :");
Logger.debug(this, toWrite);
}
if((out != null) && (socket != null) && socket.isConnected()) {
try {
bufferedOut.write(toWrite.getBytes("UTF-8"));
} catch(final java.io.UnsupportedEncodingException e) {
Logger.error(this, "UNSUPPORTED ENCODING EXCEPTION : UTF-8");
bufferedOut.write(toWrite.getBytes());
}
} else {
Logger.notice(this, "Cannot write if disconnected !");
if (out == null)
Logger.notice(this, "^ no output stream ^");
if (socket == null)
Logger.notice(this, "^ no socket ^");
else if (!socket.isConnected())
Logger.notice(this, "^ socket but not connected ^");
if (checkLock)
removeFromWriterQueue();
return false;
}
if (checkLock)
removeFromWriterQueue();
return true;
}
/**
* For security : FCPQueryManager uses this function to tells FCPConnection
* how many raw bytes are waited (to avoid to *serious* problems).
*/
public void setRawDataWaiting(final long waiting) {
rawBytesWaiting = waiting;
}
/**
* @see #read(int, byte[])
*/
public int read(final byte[] buf) {
return read(buf.length, buf);
}
/**
* @param lng Obsolete.
* @return -1 Disconnection.
*/
public int read(final int lng, final byte[] buf) {
int rdBytes = 0;
try {
rdBytes = reader.read(buf);
if(rdBytes < 0) {
Logger.error(this, "Error while reading on the socket => disconnection");
disconnect();
return rdBytes;
}
rawBytesWaiting = rawBytesWaiting - rdBytes;
//Logger.verbose(this, "Remaining: "+rawBytesWaiting);
return rdBytes;
} catch(final java.io.IOException e) {
Logger.error(this, "IOException while reading raw bytes on socket => disconnection:");
Logger.error(this, " =========");
Logger.error(this, e.getMessage() + ":");
if (e.getCause() != null)
Logger.error(this, e.getCause().toString());
Logger.error(this, e.getMessage() );
Logger.error(this, " =========");
disconnect();
return -2; /* -1 can mean eof */
}
}
/**
* Read a line.
* @return null if disconnected or error
*/
public String readLine() {
/* SECURITY */
if(rawBytesWaiting > 0) {
Logger.warning(this, "RAW BYTES STILL WAITING ON SOCKET. THIS IS ABNORMAL. -> Will drop them.");
while(rawBytesWaiting > 0) {
int to_read = 1024;
if(to_read > rawBytesWaiting)
to_read = (int)rawBytesWaiting;
final byte[] read = new byte[to_read];
this.read(to_read, read);
rawBytesWaiting = rawBytesWaiting - to_read;
}
}
String result;
if((in != null) && (reader != null) && (socket != null) && socket.isConnected()) {
try {
for(int i = 0; i < recvBytes.length ; i++)
recvBytes[i] = 0;
result = "";
int c = 0;
int i = 0; /* position in recvBytes */
while((c != '\n') && (i < recvBytes.length)) {
c = reader.read();
if(c == -1) {
if(isConnected())
Logger.warning(this, "Unable to read but still connected");
else
Logger.notice(this, "Disconnected");
disconnect(); /* will warn everybody */
return null;
}
if(c == '\n')
break;
//result = result + new String(new byte[] { (byte)c });
recvBytes[i] = (byte)c;
i++;
}
result = new String(recvBytes, 0, i, "UTF-8");
if(FCPConnection.DEBUG_MODE) {
if(result.matches("[\\-\\ \\?.a-zA-Z0-9\\,~%@/_=\\[\\]\\(\\)]*"))
Logger.debug(this, "Thaw <<< Node : "+result);
else
Logger.debug(this, "Thaw <<< Node : Unknow chars in message. Not displayed");
}
return result;
} catch (final java.io.IOException e) {
if(isConnected())
Logger.notice(this, "IOException while reading but still connected ?! : "
+e.toString()+ " ; "+e.getMessage() );
else
Logger.notice(this, "IOException. Disconnected. : "+e.toString() + " ; "
+e.getMessage());
disconnect();
return null;
}
} else {
Logger.notice(this, "Cannot read if disconnected => null");
}
return null;
}
/**
* If duplicationAllowed, returns a copy of this object, using a different
* socket and differents lock / buffer.
* If !duplicationAllowed, returns this object.
* The duplicate socket is just connected but not initialized (ClientHello, etc).
*/
public FCPConnection duplicate() {
if (!duplicationAllowed)
return this;
Logger.info(this, "Duplicating connection to the node ...");
FCPConnection newConnection;
/* upload limit is useless here, since we can't do a global limit
* on all the connections */
newConnection = new FCPConnection(nodeAddress, port, -1,
duplicationAllowed, localSocket,
autoDownload);
if (!newConnection.connect()) {
Logger.warning(this, "Unable to duplicate socket !");
return this;
}
return newConnection;
}
public void registerClientHello(FCPClientHello ch) {
clientHello = ch;
}
public FCPClientHello getClientHello() {
return clientHello;
}
}