package thaw.fcp;
import java.util.Observable;
import thaw.core.Logger;
import thaw.core.ThawThread;
import thaw.core.ThawRunnable;
/**
* Manage all fcp messages (see corresponding object of each kind of query).
* Call observers each type a new message is received. The given object is
* the message.
*/
public class FCPQueryManager extends Observable implements ThawRunnable {
private Thread me;
private FCPConnection connection;
private boolean running;
public FCPQueryManager(final FCPConnection connection) {
me = null;
setConnection(connection);
running = true;
}
/**
* If you call yourself this function, you will probably have to call
* resetQueue() of FCPQueueManager.
*/
public void setConnection(final FCPConnection connection) {
this.connection = connection;
}
/**
* Try to not directly call functions from FCPConnection.
*/
public FCPConnection getConnection() {
return connection;
}
public boolean writeMessage(final FCPMessage message) {
return connection.write(message.toString());
}
public boolean writeMessage(final FCPMessage message, final boolean checkLock) {
return connection.write(message.toString(), checkLock);
}
/**
* Blocking until a message is reveived.
* More exactly, read until "Data\n" or "EndMessage\n" is read.
*/
public FCPMessage readMessage() {
String whatsUp = new String("");
final FCPMessage result = new FCPMessage();
boolean withData;
withData = false;
while(true) {
String read = new String("");
read = connection.readLine();
if(read == null) {
Logger.notice(this, "readLine() returned null => disconnected ?");
return null;
}
if("Data".equals( read )) {
withData = true;
break;
}
if("EndMessage".equals( read )) {
break;
}
whatsUp = whatsUp + read + "\n";
}
Logger.verbose(this, "Parsing message ...");
result.loadFromRawMessage(whatsUp);
if(withData && result.getValue("DataLength") != null) {
final long dataWaiting = (new Long(result.getValue("DataLength"))).longValue();
connection.setRawDataWaiting(dataWaiting);
Logger.info(this, "Achtung data: "+(new Long(dataWaiting)).toString());
}
return result;
}
public class Notifier implements ThawRunnable {
FCPMessage msg;
public Notifier(FCPMessage msg) {
this.msg = msg;
}
public void run() {
try {
setChanged();
notifyObservers(msg);
} catch(final Exception e) {
/* it's really bad ... because if data are waiting on the socket ... */
Logger.error(this, "EXCEPTION FROM ONE OF LISTENER : "+e.toString());
Logger.error(this, "ERROR : "+e.getMessage());
e.printStackTrace();
}
}
public void stop() {
/* can't stop */
}
}
public class WatchDog implements ThawRunnable {
public final static int TIMEOUT = 10000;
ThawRunnable runnable;
public WatchDog(ThawRunnable runnable) {
this.runnable = runnable;
}
private boolean isRunning(Thread th) {
return (th.isAlive());
}
public void run() {
Thread th = new ThawThread(runnable, "FCP message processing", this);
th.start();
for (int i = 0 ; i < TIMEOUT && isRunning(th) ; i += 300) {
try {
Thread.sleep(100);
} catch(InterruptedException e) {
/* \_o< */
}
}
while (isRunning(th)) {
Logger.warning(this, "Notifier thread ('"+th.toString()+"') seems to be blocked !!");
try { Thread.sleep(TIMEOUT); } catch(InterruptedException e) { /* \_o< */ }
}
}
public void stop() {
/* will stop by itself when the child thread will stop */
}
}
/**
* Multithreading allow the use of a watchdog. It's useful to debug,
* but I recommand to desactivate it for a normal use.
*/
public final static boolean MULTITHREADED = false;
/**
* Will listen in loop for new incoming messages.
*/
public void run() {
while(running) {
FCPMessage latestMessage;
/* note : if multithreaded, stop reading when a thread is writing,
* else reading, parsing and answering messages while a thread is
* sending a big file may generate a lot of threads (and warnings because
* of a possible freeze)
*/
latestMessage = readMessage();
if (MULTITHREADED) {
connection.addToWriterQueue();
connection.removeFromWriterQueue();
}
Logger.verbose(this, "Message received. Notifying observers");
if(latestMessage != null) {
/*
* can't multithread if data are waiting
*/
if (MULTITHREADED && latestMessage.getAmountOfDataWaiting() == 0) {
Thread notifierTh = new ThawThread(new WatchDog(new Notifier(latestMessage)),
"FCP message processing watchdog",
this);
notifierTh.start();
} else {
try {
setChanged();
notifyObservers(latestMessage);
} catch(final Exception e) {
/* it's really bad ... because if data are waiting on the socket ... */
Logger.error(this, "EXCEPTION FROM ONE OF LISTENER : "+e.toString());
Logger.error(this, "ERROR : "+e.getMessage());
e.printStackTrace();
}
}
} else {
Logger.info(this, "Stopping listening");
running = false;
}
}
}
public void stop() {
Logger.info(this, "stop() : Stopping listening");
running = false;
}
/**
* Create the thread listening for incoming message.
*/
public void startListening() {
if(connection.isConnected()) {
me = new ThawThread(this, "FCP socket listener", this);
me.start();
} else {
Logger.warning(this, "Not connected, so not listening on the socket");
}
}
/**
* This function is mainly used by FCPClientGet to have a separate socket to transfer the files.
* If FCPConnection is allowed to duplicate itself, then it will duplicate it and create a dedicated FCPQueryManager for.
* A FCPClientHello is sent with the given id.
* @return This object if it cannot duplicate FCPConnection
*/
public FCPQueryManager duplicate(final String connectionId) {
FCPConnection newConnection;
FCPQueryManager queryManager;
newConnection = connection.duplicate();
if (newConnection == connection)
return this;
queryManager = new FCPQueryManager(newConnection);
queryManager.startListening();
final FCPClientHello clientHello = new FCPClientHello(queryManager, connectionId);
if (!clientHello.start(null)) {
Logger.warning(this, "ID already used ?! Using initial socket ...");
newConnection.disconnect();
return this;
}
return queryManager;
}
}