package org.myrobotlab.net;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Iterator;
import org.myrobotlab.codec.CodecUtils;
import org.myrobotlab.framework.MRLListener;
import org.myrobotlab.framework.Message;
import org.myrobotlab.framework.ServiceEnvironment;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.service.RemoteAdapter;
import org.myrobotlab.service.Runtime;
import org.myrobotlab.service.interfaces.CommunicationInterface;
import org.myrobotlab.service.interfaces.ServiceInterface;
import org.slf4j.Logger;
public class TcpThread extends Thread {
public final static Logger log = LoggerFactory.getLogger(TcpThread.class);
// FIXME - should be Gateway not Service
// FIXME - Communication interface - with logMsgs(bool)
RemoteAdapter myService;
public Socket socket;
public Connection data;
ObjectInputStream in;
ObjectOutputStream out;
boolean isRunning = false;
URI protocolKey;
URI uri; // mrl uri
// debug / logging
private transient FileOutputStream msgLog = null;
public TcpThread(RemoteAdapter service, URI uri, Socket socket) throws UnknownHostException, IOException {
super(String.format("%s:%s", service.getName(), uri));
this.myService = service;
this.data = new Connection(service.getName(), uri);
if (socket == null) {
socket = new Socket(uri.getHost(), uri.getPort());
}
this.socket = socket;
out = new ObjectOutputStream((socket.getOutputStream()));
out.flush();
in = new ObjectInputStream(socket.getInputStream());
this.start();
msgLog = new FileOutputStream(String.format("%s.%d.json", service.getName(), System.currentTimeMillis()));
}
// FIXME - prepare for re-init / or completely de-init
// and have RA re-establish connection
public void releaseConnect() {
try {
data.state = Connection.DISCONNECTED;
String instanceID = String.format("mrl://%s/%s", myService.getName(), data.protocolKey);
log.error("removing {} from registry", instanceID);
// FIXME - not working - are you sure you want to do this?
// just because the connection is broken
Runtime.release(new URI(instanceID));
log.error("shutting down thread");
isRunning = false;
log.error("attempting to close streams");
in.close();
out.close();
log.error("attempting to close socket");
socket.close();
} catch (Exception dontCare) {
}
}
public String getXforewarderName(String name){
return String.format("%s%s", myService.getPrefix(protocolKey), name);
}
/**
* listening for inbound messages
*/
@Override
public void run() {
try {
isRunning = true;
data.state = Connection.CONNECTED;
while (socket != null && isRunning) {
Message msg = null;
Object o = null;
o = in.readObject();
msg = (Message) o;
++data.rx;
// nice for debugging
if (msgLog != null) {
msgLog.write(String.format("%s <-- %s - %s\n", myService.getName(), uri, CodecUtils.toJson(msg)).getBytes());
}
data.rxSender = msg.sender;
data.rxSendingMethod = msg.sendingMethod;
data.rxName = msg.name;
data.rxMethod = msg.method;
// creating new mrluri to represent this connection... (its specific to a single client)
if (uri == null) {
protocolKey = new URI(String.format("tcp://%s:%d", socket.getInetAddress().getHostAddress(), socket.getPort()));
String mrlURI = String.format("mrl://%s/%s", myService.getName(), protocolKey.toString());
uri = new URI(mrlURI);
}
/**
* mrl works similar to - router x-forwarded in that it
* re-writes names in order to provide an abstraction to a
* remote system. This can prevent name collision and add
* clarity to remote system names - msg sender / name re-write
* are trivial - the danger & difficulty comes when names are
* embedded in the data payload - such as register, addListener
* and other(?) methods - for example - service names as
* parameters ! - which "should" only happen with incorrect user scripts..
* because registration is where all info regarding foreign service names should
* come from
*/
msg.sender = String.format("%s%s", myService.getPrefix(protocolKey), msg.sender);
// router x-forwarded inbound proxy begin
// router x-forwarded inbound proxy end
// FIXME - SCARY ! - anywhere address (name) info is in the data
// payload you will get errors & bugs :(
// getName() would need to be there of couse... I can't imagine
// how many other places ..
// Not the best implementation - an Instance would
// FIXME - HashSet of methods needed ?
// FIXME - if Encode.getMethodSignature("publishState",
// Service.class).equals(Encode.getMethodSignature(msg));
if ("publishState".equals(msg.method)) { // this is to a specific service not runtime - msg.name != null
// FIXME - normalize
// router x-forwarded inbound proxy begin
Object[] msgData = msg.data;
ServiceInterface si = null;
if (msgData != null) {
if (msg.data.length == 0) {
log.error("*** a publishState was sent without a service - you probably want to send broadcastState ! {} {}**", msg.sender, msg.data.length);
return;
}
si = (ServiceInterface) msg.data[0];
si.setInstanceId(uri);
String xForwardDataName = String.format("%s%s", myService.getPrefix(protocolKey), si.getName());
si.setName(xForwardDataName);
}
// router x-forwarded inbound proxy end
}
if ("onState".equals(msg.method)) { // this is to a specific service not runtime - msg.name != null
// FIXME - normalize
// router x-forwarded inbound proxy begin
Object[] msgData = msg.data;
ServiceInterface si = null;
if (msgData != null) {
if (msg.data.length == 0) {
log.error("*** a publishState was sent without a service - you probably want to send broadcastState ! {} {}**", msg.sender, msg.data.length);
return;
}
si = (ServiceInterface) msg.data[0];
si.setInstanceId(uri);
String xForwardDataName = String.format("%s%s", myService.getPrefix(protocolKey), si.getName());
si.setName(xForwardDataName);
}
// router x-forwarded inbound proxy end
}
// establishing a callback route - src needs xforward modification
if ("addListener".equals(msg.method)) { // this is to a specific service not runtime - msg.name != null
MRLListener listener = (MRLListener) msg.data[0];
listener.callbackName = msg.sender;
}
// FIXME - THIS NEEDS TO BE NORMALIZED - WILL BE THE SAME IN
// XMPP & WEBGUI & REMOTEADAPTER
// FIXME - normalize to single method - check for data
// type too ? !!!
if (msg.method.equals("onRegistered")) {
Object[] msgData = msg.data;
ServiceInterface si = null;
// ALLOWED TO BE NULL - establishes initial contact & a
// ServiceEnvironment
if (msgData != null) {
si = (ServiceInterface) msg.data[0];
si.setInstanceId(uri);
String xForwardDataName = String.format("%s%s", myService.getPrefix(protocolKey), si.getName());
si.setName(xForwardDataName);
myService.send(Runtime.getInstance().getName(), "register", si, uri);
}
}
if (msg.method.equals("register")) {
// create the URI key for foreign service environment
// IMPORTANT - this is an optimization and probably
// should be in the Comm interface defintion
CommunicationInterface cm = myService.getComm();
cm.addRemote(uri, protocolKey);
// check if the URI is already defined - if not - we will
// send back the services which we want to export -
// Security will filter appropriately
ServiceEnvironment foreignEnvironment = Runtime.getEnvironment(uri);
// FIXME - normalize ...
Object[] msgData = msg.data;
ServiceInterface si = null;
// ALLOWED TO BE NULL - establishes initial contact & a
// ServiceEnvironment
if (msgData != null) {
si = (ServiceInterface) msg.data[0];
si.setInstanceId(uri);
String xForwardDataName = String.format("%s%s", myService.getPrefix(protocolKey), si.getName());
si.setName(xForwardDataName);
}
// invoke directly or send msg - we've gone both ways
// if invoke directly - security/control must be employed here
myService.send(Runtime.getInstance().getName(), "register", si, uri);
// if is a foreign process - send our registration
if (foreignEnvironment == null) {
// not defined we will send export
// TODO - Security filters - default export (include
// exclude) - mapset of name
ServiceEnvironment localProcess = Runtime.getLocalServicesForExport();
Iterator<String> it = localProcess.serviceDirectory.keySet().iterator();
String name;
ServiceInterface toRegister;
while (it.hasNext()) {
name = it.next();
toRegister = localProcess.serviceDirectory.get(name);
// the following will wrap a message within a
// message and send it remotely
// This Thread CANNOT Write on The
// ObjectOutputStream directly -
// IT SHOULD NEVER DO ANY METHOD WHICH CAN BLOCK
// !!!! - 3 days of bug chasing when
// it wrote to ObjectOutputStream and oos blocked
// when the buffer was full - causing deadlock
// putting it on the inbox will move it to a
// different thread
Message sendService = myService.createMessage(null, "register", toRegister);
Message outbound = myService.createMessage(myService.getName(), "sendRemote", new Object[] { protocolKey, sendService });
myService.getInbox().add(outbound);
}
}
// BEGIN ENCAPSULATION --- ENCODER END -------------
} else {
myService.getOutbox().add(msg);
++data.rx;
}
} // while
} catch (Exception e) {
isRunning = false;
socket = null;
Logging.logError(e);
data.state = Connection.DISCONNECTED;
}
releaseConnect();
}
// FIXME - merge with RemoteAdapter - this is just sendRemote
public synchronized void send(Message msg) {
try {
// router x-forwarded outbound proxy begin
// TODO - optimize - set once ! same with prefix .. +1 for the
// String.format("%s.", n) period !
if (msg.name != null) {
msg.name = msg.name.substring(myService.getPrefix(protocolKey).length());
}
// router x-forwarded outbound proxy end
// nice for debugging
if (msgLog != null) {
msgLog.write(String.format("%s --> %s - %s\n", myService.getName(), uri, CodecUtils.toJson(msg)).getBytes());
}
out.writeObject(msg);
out.flush();
// MAKE NOTE !!! :
// a reset is necessary after every object !
out.reset();
data.txSender = msg.sender;
data.txSendingMethod = msg.sendingMethod;
data.txName = msg.name;
data.txMethod = msg.method;
++data.tx;
} catch (Exception e) {
myService.error(e);
releaseConnect();
}
}
}