aQute.remote.util.Link Maven / Gradle / Ivy
The newest version!
package aQute.remote.util;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import aQute.lib.json.JSONCodec;
/**
* This is a simple RPC module that has a R and L interface. The R interface is
* implemented on the remote side. The methods on this subclass are then
* available remotely. I.e. this is a two way street. Void messages are
* asynchronous, other messages block to a reply.
*
* @param
*/
public class Link extends Thread implements Closeable {
private static final String[] EMPTY = new String[] {};
static JSONCodec codec = new JSONCodec();
final DataInputStream in;
final DataOutputStream out;
final Class remoteClass;
final AtomicInteger id = new AtomicInteger(10000);
final ConcurrentMap promises = new ConcurrentHashMap<>();
final AtomicBoolean quit = new AtomicBoolean(false);
volatile boolean transfer = false;
private ThreadLocal msgid = new ThreadLocal<>();
R remote;
L local;
ExecutorService executor = Executors.newFixedThreadPool(4);
static class Result {
boolean resolved;
byte[] value;
public boolean exception;
}
public Link(Class remoteType, L local, InputStream in, OutputStream out) {
this(remoteType, local, new DataInputStream(in), new DataOutputStream(out));
}
@SuppressWarnings("unchecked")
public Link(Class remoteType, L local, DataInputStream in, DataOutputStream out) {
super("link::" + remoteType.getName());
setDaemon(true);
this.remoteClass = remoteType;
this.local = local == null ? (L) this : local;
this.in = new DataInputStream(in);
this.out = new DataOutputStream(out);
}
public Link(Class type, L local, Socket socket) throws IOException {
this(type, local, socket.getInputStream(), socket.getOutputStream());
}
public void open() {
if (isAlive())
throw new IllegalStateException("Already running");
if (in != null)
start();
}
@Override
public void close() throws IOException {
if (quit.getAndSet(true) == true)
return; // already closed
if (local instanceof Closeable)
try {
((Closeable) local).close();
} catch (Exception e) {}
if (!transfer) {
if (in != null)
try {
in.close();
} catch (Exception e) {}
if (out != null)
try {
out.close();
} catch (Exception e) {}
}
executor.shutdownNow();
}
@SuppressWarnings("unchecked")
public synchronized R getRemote() {
if (quit.get())
return null;
if (remote == null)
remote = (R) Proxy.newProxyInstance(remoteClass.getClassLoader(), new Class>[] {
remoteClass
}, new InvocationHandler() {
@Override
public Object invoke(Object target, Method method, Object[] args) throws Throwable {
Object hash = new Object();
try {
if (method.getDeclaringClass() == Object.class)
return method.invoke(hash, args);
int msgId;
try {
msgId = send(id.getAndIncrement(), method, args);
if (method.getReturnType() == void.class) {
promises.remove(msgId);
return null;
}
} catch (Exception e) {
terminate(e);
return null;
}
return waitForResult(msgId, method.getGenericReturnType());
} catch (InvocationTargetException e) {
Throwable t = e;
while (t instanceof InvocationTargetException)
t = ((InvocationTargetException) t).getTargetException();
throw t;
} catch (InterruptedException e) {
interrupt();
throw e;
} catch (Exception e) {
throw e;
}
}
});
return remote;
}
@Override
public void run() {
while (!isInterrupted() && !transfer && !quit.get())
try {
final String cmd = in.readUTF();
trace("rx " + cmd);
final int id = in.readInt();
int count = in.readShort();
final List args = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
int length = in.readInt();
byte[] data = new byte[length];
in.readFully(data);
args.add(data);
}
Runnable r = new Runnable() {
@Override
public void run() {
try {
msgid.set(id);
executeCommand(cmd, id, args);
} catch (Exception e) {
// e.printStackTrace();
}
msgid.set(-1);
}
};
executor.execute(r);
} catch (SocketTimeoutException ee) {
// Ignore, just to allow polling the actors again
} catch (Exception ee) {
terminate(ee);
return;
}
}
/*
* Signalling function /
*/
protected void terminate(Exception t) {
try {
close();
} catch (IOException e) {}
}
Method getMethod(String cmd, int count) {
for (Method m : local.getClass()
.getMethods()) {
if (m.getDeclaringClass() == Link.class)
continue;
if (m.getName()
.equals(cmd) && m.getParameterTypes().length == count) {
return m;
}
}
return null;
}
int send(int msgId, Method m, Object args[]) throws Exception {
if (m != null)
promises.put(msgId, new Result());
trace("send");
synchronized (out) {
out.writeUTF(m != null ? m.getName() : "");
out.writeInt(msgId);
if (args == null)
args = EMPTY;
out.writeShort(args.length);
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof byte[]) {
byte[] data = (byte[]) arg;
out.writeInt(data.length);
out.write(data);
} else {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
codec.enc()
.to(bout)
.put(arg);
byte[] data = bout.toByteArray();
out.writeInt(data.length);
out.write(data);
}
}
out.flush();
trace("sent");
}
return msgId;
}
void response(int msgId, byte[] data) {
boolean exception = false;
if (msgId < 0) {
msgId = -msgId;
exception = true;
}
Result o = promises.get(msgId);
if (o != null) {
synchronized (o) {
trace("resolved");
o.value = data;
o.exception = exception;
o.resolved = true;
o.notifyAll();
}
}
}
@SuppressWarnings("unchecked")
T waitForResult(int id, Type type) throws Exception {
long deadline = System.currentTimeMillis() + 300000;
Result result = promises.get(id);
try {
do {
synchronized (result) {
if (result.resolved) {
if (result.value == null)
return null;
if (result.exception) {
String msg = codec.dec()
.from(result.value)
.get(String.class);
System.out.println("Exception " + msg);
throw new RuntimeException(msg);
}
if (type == byte[].class)
return (T) result.value;
T value = (T) codec.dec()
.from(result.value)
.get(type);
return value;
}
long delay = deadline - System.currentTimeMillis();
if (delay <= 0) {
return null;
}
trace("start delay " + delay);
result.wait(delay);
trace("end delay " + (delay - (deadline - System.currentTimeMillis())));
}
} while (true);
} finally {
promises.remove(id);
}
}
private void trace(String string) {
// TODO Auto-generated method stub
}
/*
* Execute a command in a background thread
*/
void executeCommand(final String cmd, final int id, final List args) throws Exception {
if (cmd.isEmpty())
response(id, args.get(0));
else {
Method m = getMethod(cmd, args.size());
if (m == null) {
return;
}
Object parameters[] = new Object[args.size()];
for (int i = 0; i < args.size(); i++) {
Class> type = m.getParameterTypes()[i];
if (type == byte[].class)
parameters[i] = args.get(i);
else {
parameters[i] = codec.dec()
.from(args.get(i))
.get(m.getGenericParameterTypes()[i]);
}
}
try {
Object result = m.invoke(local, parameters);
if (transfer || m.getReturnType() == void.class)
return;
try {
send(id, null, new Object[] {
result
});
} catch (Exception e) {
terminate(e);
return;
}
} catch (Throwable t) {
while (t instanceof InvocationTargetException
&& ((InvocationTargetException) t).getTargetException() != null)
t = ((InvocationTargetException) t).getTargetException();
// t.printStackTrace();
try {
send(-id, null, new Object[] {
t + ""
});
} catch (Exception e) {
terminate(e);
return;
}
}
}
}
public boolean isOpen() {
return !quit.get();
}
public DataOutputStream getOutput() {
assert transfer && !isOpen();
return out;
}
public DataInputStream getInput() {
assert transfer && !isOpen();
return in;
}
@SuppressWarnings("unchecked")
public void setRemote(Object remote) {
this.remote = (R) remote;
}
public void transfer(Object result) throws Exception {
transfer = true;
quit.set(true);
interrupt();
join();
if (result != null)
send(msgid.get(), null, new Object[] {
result
});
close();
}
}