package com.jsonde.client.network;
import com.jsonde.api.Message;
import com.jsonde.api.MessageListener;
import com.jsonde.api.function.FunctionRequest;
import com.jsonde.api.function.FunctionResponse;
import com.jsonde.util.log.Log;
import java.io.IOException;
import java.net.Socket;
import java.util.Collection;
import java.util.Vector;
import java.util.concurrent.*;
public class NetworkClientImpl implements NetworkClient {
private static final Log log = Log.getLog(NetworkClientImpl.class);
private String address;
private int port;
private boolean running;
private boolean outputWorkerReady;
private boolean inputWorkerReady;
private BlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(5);
private Socket socket;
private Thread outputThread;
private Thread inputThread;
private Collection<MessageListener> messageListeners = new Vector<MessageListener>();
public NetworkClientImpl(String address, int port) {
this.address = address;
this.port = port;
}
public void start() throws NetworkClientException {
try {
socket = new Socket(address, port);
} catch (IOException e) {
throw new NetworkClientException(e);
}
setRunning(true);
inputThread = new Thread(new ClientInputWorker(this, socket));
inputThread.setDaemon(true);
inputThread.start();
outputThread = new Thread(new ClientOutputWorker(this, socket));
outputThread.setDaemon(true);
outputThread.start();
waitForInitialization();
}
public void stop() throws NetworkClientException {
final String METHOD_NAME = "stop()";
setRunning(false);
try {
outputThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(METHOD_NAME, e);
}
try {
inputThread.join(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(METHOD_NAME, e);
}
if (inputThread.isAlive()) {
closeSockets();
try {
inputThread.join(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(METHOD_NAME, e);
}
if (inputThread.isAlive()) {
inputThread.interrupt();
}
} else {
closeSockets();
}
processMessageExecutorService.shutdown();
try {
processMessageExecutorService.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(METHOD_NAME, e);
}
}
private synchronized void closeSockets() throws NetworkClientException {
try {
socket.close();
} catch (IOException e) {
throw new NetworkClientException(e);
}
notifyAll();
}
private synchronized void waitForInitialization() {
final String METHOD_NAME = "waitForInitialization()";
while (!areWorkersReady()) {
try {
wait();
} catch (InterruptedException e) {
log.error(METHOD_NAME, e);
Thread.currentThread().interrupt();
}
}
notifyAll();
}
public synchronized void sendMessage(Message message) {
final String METHOD_NAME = "sendMessage(Message)";
try {
boolean inserted = false;
while (!inserted) {
inserted = messageQueue.offer(message);
if (inserted) {
notifyAll();
} else {
wait();
}
}
} catch (InterruptedException e) {
log.error(METHOD_NAME, e);
Thread.currentThread().interrupt();
}
}
public <T extends FunctionResponse> T invokeFunction(final FunctionRequest<T> functionRequest)
throws InterruptedException {
final SynchronousQueue<T> functionResponseExchanger =
new SynchronousQueue<T>();
addMessageListener(new MessageListener() {
public void onMessage(Message message) {
if (message instanceof FunctionResponse) {
T functionResponse = (T) message;
if (functionResponse.getRequestId() == functionRequest.getRequestId()) {
functionResponseExchanger.offer(functionResponse);
}
}
}
});
sendMessage(functionRequest);
return functionResponseExchanger.take();
}
protected synchronized boolean isMessageInQueue() throws InterruptedException {
while (isRunning() && 0 == messageQueue.size()) {
wait();
}
return 0 != messageQueue.size();
}
protected synchronized Message takeMessageFromQueue() {
Message message = messageQueue.poll();
notifyAll();
return message;
}
private ExecutorService processMessageExecutorService = Executors.newSingleThreadExecutor();
protected void processMessage(final Message message) {
processMessageExecutorService.submit(new Runnable() {
public void run() {
for (MessageListener messageListener : messageListeners) {
messageListener.onMessage(message);
}
}
});
}
public void addMessageListener(MessageListener messageListener) {
messageListeners.add(messageListener);
}
public void removeMessageListener(MessageListener messageListener) {
messageListeners.remove(messageListener);
}
public synchronized boolean isRunning() {
return running;
}
public synchronized void setRunning(boolean running) {
this.running = running;
notifyAll();
}
private synchronized boolean areWorkersReady() {
return inputWorkerReady && outputWorkerReady;
}
public synchronized void setOutputWorkerReady(boolean outputWorkerReady) {
this.outputWorkerReady = outputWorkerReady;
notifyAll();
}
public synchronized void setInputWorkerReady(boolean inputWorkerReady) {
this.inputWorkerReady = inputWorkerReady;
notifyAll();
}
}